package com.yanqu.road.server.manager.activity.daomu.pvp;


import com.alibaba.fastjson.JSON;
import com.yanqu.road.entity.activity.daomu.config.*;
import com.yanqu.road.entity.activity.daomu.enums.DaoMuAttributeEnum;
import com.yanqu.road.entity.activity.daomu.enums.DaoMuCurioTypeEnum;
import com.yanqu.road.entity.activity.daomu.enums.pvp.DaoMuPvpNoticeType;
import com.yanqu.road.entity.activity.daomu.pvp.*;
import com.yanqu.road.entity.config.system.NormalWeightItem;
import com.yanqu.road.entity.enums.eGamePlayerEventType;
import com.yanqu.road.entity.log.cross.daomu.LogDaoMuPvpApply;
import com.yanqu.road.entity.log.cross.daomu.LogDaoMuPvpKill;
import com.yanqu.road.entity.log.cross.daomu.LogDaoMuPvpPick;
import com.yanqu.road.entity.log.cross.daomu.LogDaoMuPvpSettle;
import com.yanqu.road.entity.player.CrossUserBaseInfo;
import com.yanqu.road.entity.player.UserBaseInfo;
import com.yanqu.road.entity.rank.cross.CrossUserRank;
import com.yanqu.road.logic.bussiness.activity.DaoMuActivityBusiness;
import com.yanqu.road.logic.helper.RandomWeightConfigHelper;
import com.yanqu.road.logic.helper.WeightHelper;
import com.yanqu.road.logic.pb.PlayerBasePb;
import com.yanqu.road.pb.activity.DaoMuProto;
import com.yanqu.road.pb.activity.DaoMuPvpProto;
import com.yanqu.road.server.manager.activity.ActivityRankMgr;
import com.yanqu.road.server.manager.activity.daomu.pb.DaoMuActivityPvpPb;
import com.yanqu.road.server.manager.activity.daomu.pvp.enums.DaoMuPvpBuffType;
import com.yanqu.road.entity.activity.daomu.pvp.helper.CollisionHelper;
import com.yanqu.road.server.manager.activity.daomu.pvp.rank.DaoMuAliveRankSort;
import com.yanqu.road.server.manager.activity.daomu.pvp.rank.DaoMuRank;
import com.yanqu.road.server.manager.activity.daomu.pvp.rank.DaoMuRankListModel;
import com.yanqu.road.server.manager.log.CrossAutoLogMgr;
import com.yanqu.road.server.manager.npc.CrossNpcUserMgr;
import com.yanqu.road.server.manager.user.CrossUserMgr;
import com.yanqu.road.server.protocol.GameErrorCode;
import com.yanqu.road.server.protocol.Protocol;
import com.yanqu.road.utils.ConfigHelper;
import com.yanqu.road.utils.date.DateHelper;
import com.yanqu.road.utils.property.Property;
import com.yanqu.road.utils.property.PropertyHelper;
import com.yanqu.road.utils.string.StringUtils;
import org.apache.mina.util.ConcurrentHashSet;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static com.yanqu.road.entity.activity.daomu.enums.DaoMuAttributeEnum.*;
import static com.yanqu.road.entity.activity.daomu.pvp.helper.CoordinateHelper.*;

public class DaoMuActivityPvp extends DaoMuActivityPvpBase {

    // 活动数据
    private DaoMuPvpData daoMuPvpData;

    /**
     * 本轮报名名单 ,每轮重置
     */
    private DaoMuRankListModel applyRankListModel = new DaoMuRankListModel();

    /**
     * 本轮存活榜单 最后死亡时间(更新时间)、等级、经验 ,每轮重置
     */
    private DaoMuRankListModel aliveRankListModel = new DaoMuRankListModel(new DaoMuAliveRankSort());

    /**
     * 存活人数,每轮重置
     */
    private AtomicInteger aliveCnt;


    // 房间信息
    private Map<Integer, DaoMuPvpRoomData> roomDataMap = new ConcurrentHashMap<>();

    // 玩家信息
    private Map<Long, DaoMuPvpUserData> userDataMap = new ConcurrentHashMap<>();


    /**
     * 待同步的位置信息
     */
    private Map<Integer, Map<Long, DaoMuPvpPosition>> needSyncPlayerPositionMap = new ConcurrentHashMap<>();

    /**
     * 待同步的怪物位置信息
     */
    private Map<Integer, Map<String, DaoMuPvpPosition>> needSyncMonsterPositionMap = new ConcurrentHashMap<>();


    private Map<Integer, Map<String, Integer>> needSyncRemoveRoomItemMap = new ConcurrentHashMap<>();

    /**
     * 玩家锁
     */
    private static final Map<Long, Object> playerLockMap = new ConcurrentHashMap<>();

    /**
     * 房间锁
     */
    private static final Map<Integer, Object> roomLockMap = new ConcurrentHashMap<>();

    /**
     * 获取玩家锁
     */
    private static Object getPlayerLock(long userId) {
        if (!playerLockMap.containsKey(userId)) {
            synchronized (playerLockMap) {
                if (!playerLockMap.containsKey(userId)) {
                    playerLockMap.put(userId, new Object());
                }
            }
        }
        return playerLockMap.get(userId);
    }


    /**
     * 获取房间锁
     */
    private static Object getRoomLock(int roomId) {
        if (!roomLockMap.containsKey(roomId)) {
            synchronized (roomLockMap) {
                if (!roomLockMap.containsKey(roomId)) {
                    roomLockMap.put(roomId, new Object());
                }
            }
        }
        return roomLockMap.get(roomId);
    }

    /**
     * 房间玩家
     */
    private Map<Integer, ConcurrentHashSet<Long>> roomUserMap = new ConcurrentHashMap<>();

    private void addRoomUser(int roomId, long userId) {
        synchronized (roomUserMap) {
            ConcurrentHashSet<Long> userSet = roomUserMap.getOrDefault(roomId, new ConcurrentHashSet<>());
            userSet.add(userId);
            roomUserMap.put(roomId, userSet);
        }
    }

    private void removeRoomUser(int roomId, long userId) {
        synchronized (roomUserMap) {
            ConcurrentHashSet<Long> userSet = roomUserMap.getOrDefault(roomId, new ConcurrentHashSet<>());
            userSet.remove(userId);
            roomUserMap.put(roomId, userSet);
        }
    }

    /**
     * 获取房间用户数据
     *
     * @param roomId 房间id
     * @return
     */
    private ConcurrentHashSet<Long> getRoomUserList(int roomId) {
        return new ConcurrentHashSet<>(roomUserMap.getOrDefault(roomId, new ConcurrentHashSet<>()));
    }


    /**
     * 消息同步调用这个方法！！！
     * <p>
     * 排除心跳时间大于10s的
     *
     * @param roomId 房间id  id 传-1则获取活动内所有玩家
     */
    private ConcurrentHashSet<Long> getRoomUserListSync(int roomId) {
        ConcurrentHashSet<Long> list = new ConcurrentHashSet<>();
        long nowTime = System.currentTimeMillis();
        ConcurrentHashSet<Long> roomUserList;
        if (roomId == -1) {
            roomUserList = new ConcurrentHashSet<>(userDataMap.keySet());
        } else {
            roomUserList = getRoomUserList(roomId);
        }
        for (Long targetUserId : roomUserList) {
            DaoMuPvpUserData userData = getUserData(targetUserId);
            if (userData == null || !userData.isApply() || !userData.isAble() || userData.isOut() || npcSet.contains(userData.getUserId())) {
                continue;
            }
            if (userData.getPingTime() == -1 || nowTime - userData.getPingTime() > 10 * DateHelper.SECOND_MILLIONS) {
                continue;
            }
            list.add(targetUserId);
        }
        return list;
    }

    public DaoMuActivityPvp(DaoMuActivityConfig config) {
        super(config);
        // 读数据
        int activityId = config.getActivityId();
        // 当前轮次
        int curRound = getCurRound();
        daoMuPvpData = DaoMuActivityBusiness.getDaoMuPvpData(activityId);
        if (daoMuPvpData == null) {
            // 初始化
            daoMuPvpData = new DaoMuPvpData();
            daoMuPvpData.setActivityId(activityId);
            daoMuPvpData.setRound(curRound);
            daoMuPvpData.setLockState(false);
            daoMuPvpData.setFloorCntMap(new ConcurrentHashMap<>());
            daoMuPvpData.setDisuse(false);
            daoMuPvpData.setSettle(false);
            daoMuPvpData.setInsertOption();
        }
        // pvp玩家数据
        userDataMap = DaoMuActivityBusiness.getDaoMuPvpUserDataMap(activityId);
        // 房间数据
        roomDataMap = DaoMuActivityBusiness.getDaoMuPvpRoomDataMap(activityId);
        // 初始化房间数据,不存在的则创建
        for (DaoMuPvpConfig daoMuPvpConfig : config.getPvpConfigMap().values()) {
            if (daoMuPvpConfig == null) {
                continue;
            }
            int floor = daoMuPvpConfig.getId();
            // 获取房间配置
            for (Integer roomId : daoMuPvpConfig.getConnectedRoomMap().keySet()) {
                if (!roomDataMap.containsKey(roomId)) {
                    createPvpRoom(floor, roomId);
                }
            }
        }
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            // 初始化些房间数据
            initRoomData(roomData);
            if (roomData.isCollapse()) {
                // 有一个房间塌了，视为本层全塌
                daoMuPvpData.getCollapseFloors().add(roomData.getFloor());
            }
        }
        // 加到内存房间里
        for (DaoMuPvpUserData userData : userDataMap.values()) {
            if (userData.isApply() && userData.isAble() && !userData.isOut() && userData.getRoomId() != 0) {
                DaoMuPvpRoomData roomData = getRoomData(userData.getRoomId());
                if (!roomData.isCollapse()) {
                    addRoomUser(roomData.getRoomId(), userData.getUserId());
                }
            }
        }
        // 初始化榜单
        initAllRank();
    }

    private void initAllRank() {
        synchronized (lock) {
            long nowTime = System.currentTimeMillis();
            int liveCnt = 0;
            for (DaoMuPvpUserData userData : userDataMap.values()) {
                if (userData.isApply()) {
                    if (userData.isAble()) {
                        long liveTime = nowTime;
                        if (userData.isOut()) {
                            liveTime = userData.getOutTime();
                        } else {
                            // 还活着的
                            liveCnt++;
                        }
                        // param 等级#经验#经验更新时间#锁定时藏品分
                        List<String> paramList = new ArrayList<>();
                        paramList.add(String.valueOf(userData.getProperty().getLevel()));
                        paramList.add(String.valueOf(userData.getProperty().getExp()));
                        paramList.add(String.valueOf(userData.getProperty().getExpUpdateTime()));
                        paramList.add(String.valueOf(userData.getCollectionScore()));
                        aliveRankListModel.rankChange(new DaoMuRank(userData.getUserId(), liveTime, StringUtils.listToString(paramList, "#"), liveTime));
                    }
                    applyRankListModel.rankChange(new DaoMuRank(userData.getUserId(), userData.getCollectionLevel(), String.valueOf(userData.getCollectionScore()), userData.getPveUploadTime()));
                }
            }
            aliveCnt = new AtomicInteger(liveCnt);
        }
    }

    private void createPvpRoom(int floor, int roomId) {
        DaoMuPvpRoomData roomData = new DaoMuPvpRoomData();
        roomData.setActivityId(config.getActivityId());
        roomData.setRoomId(roomId);
        roomData.setFloor(floor);
        roomData.setCollapse(false);
        roomData.setShrinkStartTime(0);

        roomData.setInsertOption();
        roomDataMap.put(roomData.getRoomId(), roomData);
    }

    /**
     * 初始化房间数据
     *
     * @param roomData
     */
    public void initRoomData(DaoMuPvpRoomData roomData) {
        if (roomData == null) {
            return;
        }
        // 已坍塌的不需要处理
        if (roomData.isCollapse()) {
            return;
        }
        DaoMuPvpConfig daoMuPvpConfig = config.getPvpConfigMap().get(roomData.getFloor());
        if (daoMuPvpConfig == null) {
            return;
        }
        int birthPoint = daoMuPvpConfig.getBirthPoint();
        // 出生点角度
        double angle = 360.0 / birthPoint;
        double initAngle = 0;
        List<DaoMuPvpBirthData> birthDataList = new ArrayList<>();
        for (int i = 0; i < birthPoint; i++) {
            // 将角度转换为弧度
            double rad = Math.toRadians(initAngle);
            // 计算圆上的点的坐标
            double xCoordinate = config.getPvpCommonConfig().getBirthRadius() * Math.cos(rad);
            double yCoordinate = config.getPvpCommonConfig().getBirthRadius() * Math.sin(rad);
            DaoMuPvpBirthData birthData = new DaoMuPvpBirthData();
            birthData.setRoomId(roomData.getRoomId());
            birthData.setCoordinate(new Coordinate(xCoordinate, yCoordinate));
            birthData.setBirthCnt(0);
            birthDataList.add(birthData);
            initAngle += angle;
        }
        roomData.setBirthDataList(birthDataList);
        //  生成宝箱数量
        int boxCnt = daoMuPvpConfig.getBoxNum();
        // 计算需要划分多少个区域
        int nextSquare = getNextSquare(boxCnt);
        List<Coordinate> boxCoordinateList = calcBoxCoordinateList(nextSquare, boxCnt);
        // 生成宝箱
        List<DaoMuPvpBoxData> boxDataList = new ArrayList<>();
        for (Coordinate coordinate : boxCoordinateList) {
            DaoMuPvpBoxData boxData = new DaoMuPvpBoxData();
            boxData.setUid(randomUUID());
            boxData.setCoordinate(coordinate);
            List<Integer> goodsList = new ArrayList<>();
            // 宝箱奖励
            Property property = PropertyHelper.parseNewStringToProperty(daoMuPvpConfig.getBoxWeights());
            if (!property.isNothing()) {
                for (Map.Entry<Integer, BigInteger> entry : property.getGoods().entrySet()) {
                    for (int i = 0; i < entry.getValue().intValue(); i++) {
                        goodsList.add(entry.getKey());
                    }
                }
            }
            boxData.setGoodsList(goodsList);
            boxDataList.add(boxData);
        }
        roomData.setBoxDataList(boxDataList);
    }

    private List<Coordinate> calcBoxCoordinateList(int totalCnt, int needCnt) {
        List<Coordinate> list = new ArrayList<>();
        int n = (int) Math.sqrt(totalCnt);
        double areaWidth = config.getPvpCommonConfig().getBoxAreaWidth() / n;
        double areaHeight = config.getPvpCommonConfig().getBoxAreaHeight() / n;

        List<Integer> areas = new ArrayList<>();
        for (int i = 0; i < totalCnt; i++) {
            areas.add(i);
        }

        for (int i = 0; i < needCnt; i++) {
            int areaIndex = randomHelper.next(areas.size());
            int area = areas.remove(areaIndex);
            double areaX = (area % n) * areaWidth;
            double areaY = (area / n) * areaHeight;
            Coordinate coordinate = new Coordinate();
            coordinate.setX(calcGoodsX(areaX + randomHelper.nextDouble(0, 1) * areaWidth - config.getPvpCommonConfig().getBoxAreaWidth() / 2));
            coordinate.setY(calcGoodsY(areaY + randomHelper.nextDouble(0, 1) * areaHeight - config.getPvpCommonConfig().getBoxAreaHeight() / 2));
            list.add(coordinate);
        }
        return list;
    }

    /**
     * 获取当前为第几轮
     */
    public int getCurRound() {
        return DateHelper.calc2DateTDOADays(new Date(config.getActivityInfo().getBeginTime() * 1000), new Date());
    }

    public void daoMuActivityPvpEmptyTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
//        logger.info("daoMuActivityPvpEmptyTask");
    }

    /**
     * 复活定时任务
     */
    public void daoMuActivityPvpReviveTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        // 开启了淘汰不再处理    （淘汰模式下死亡能继续复活，直到淘汰）
//        if (daoMuPvpData.isDisuse()) {
//            return;
//        }
        for (Integer roomId : roomDataMap.keySet()) {
            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomId);
            for (Long userId : roomUserList) {
                handleRevive(getUserData(userId));
            }
        }
    }

    public void daoMuPvpNpcMoveTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        for (Long userId : npcSet) {
            DaoMuPvpUserData userData = getUserData(userId);
            if (userData == null || !userData.isLive()) {
                continue;
            }
            // 位置移动
            npcMoveHandler(userData);
        }
    }

    public void daoMuPvpNpcActionTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        for (Long userId : npcSet) {
            DaoMuPvpUserData userData = getUserData(userId);
            if (userData == null || !userData.isLive()) {
                continue;
            }
            // 更新下npc的方向
            Coordinate direction = randomDir();
            userData.getPosition().setDirX(direction.getX());
            userData.getPosition().setDirY(direction.getY());
            userData.getPosition().setTime(nowTime);
            // 广播下
            addPlayerPosition(userData.getRoomId(), userData.getUserId(), userData.getPosition());

            // 加经验
            addExp(userData,randomHelper.next(100,3000));

            // 判断npc是否能进入下一层
            if (distance(userData.getCoordinate(), ZERO_COORDINATE) < 100) {
                DaoMuPvpConfig pvpConfig = config.getPvpConfigMap().get(getFloorByRoom(userData.getRoomId()));
                if (pvpConfig != null && userData.getProperty().getLevel() >= pvpConfig.getUpgradeNeedLevel()) {
                    // 进入下一层
                    enterNextFloorByCmd(userData.getUserId());
                }
            }
        }
    }

    /**
     * 创建怪物
     */
    public DaoMuPvpMonsterData createMonster(int roomId, int monsterConfigId, int attackCnt) {
        DaoMuNpcConfig npcConfig = config.getDaoMuNpcConfigMap().get(monsterConfigId);
        if (npcConfig == null) {
            return null;
        }
        DaoMuPvpMonsterData monsterData = new DaoMuPvpMonsterData();
        monsterData.setRoomId(roomId);
        monsterData.setMonsterId(randomUUID());
        monsterData.setFaceId(monsterConfigId);
        monsterData.setAttackCnt(attackCnt);

        // 怪物属性
        List<Integer> paramList = StringUtils.stringToIntegerList(npcConfig.getBasePara(), "\\|");
        // 攻击
        monsterData.setAttack(paramList.get(1));
        // 移速
        monsterData.setSpeed(paramList.get(3));

        // 池子索引
        monsterData.setPoolIndex(randomHelper.next(0, 10));

        // 怪物位置
        DaoMuPvpPosition position = new DaoMuPvpPosition();
        position.setX(randomHelper.nextDouble(roomMinX(), roomMaxX()));
        position.setY(randomHelper.nextDouble(roomMinY(), roomMaxY()));
        Coordinate coordinate = randomDir();
        position.setDirX(coordinate.getX());
        position.setDirY(coordinate.getY());
        position.setTime(System.currentTimeMillis());
        monsterData.setPosition(position);
        return monsterData;
    }

    public void daoMuPvpCreateMonsterTask() {
        if (!isPreBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                continue;
            }
            // 没人也生成
//            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomData.getRoomId());
//            if (roomUserList.isEmpty()) {
//                // 没人直接不生成
//                continue;
//            }
            // 计算需要生成多少只
            int needCreateCnt = config.getPvpCommonConfig().getMobsNum() - roomData.getMonsterDataMap().size();
            if (needCreateCnt <= 0) {
                // 超过最大数量则不生成
                continue;
            }
            // 添加怪物
            DaoMuPvpConfig pvpConfig = config.getPvpConfigMap().get(roomData.getFloor());
            if (pvpConfig == null) {
                continue;
            }
            List<DaoMuPvpMonsterData> list = new ArrayList<>();
            while (list.size() < needCreateCnt) {
                Property item = RandomWeightConfigHelper.getPropertyWeightItem(pvpConfig.getMobsWeightList());
                for (Map.Entry<Integer, BigInteger> entry : item.getGoods().entrySet()) {
                    DaoMuPvpMonsterData monsterData = createMonster(roomData.getRoomId(), entry.getKey(), entry.getValue().intValue());
                    if (monsterData != null) {
                        // 加到房间里
                        roomData.addMonsterData(monsterData);
                        // 加到同步里
                        list.add(monsterData);
                    }
                }
            }
            // 同步下
            DaoMuPvpProto.DaoMuPvpMonsterAddSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpMonsterAddSync.newBuilder();
            syncMsg.setRoomId(roomData.getRoomId());
            for (DaoMuPvpMonsterData monsterData : list) {
                syncMsg.addMonster(DaoMuActivityPvpPb.parseDaoMuPvpMonsterTemp(monsterData));
            }
            sendPacketToAll(Protocol.U_DAOMU_PVP_MONSTER_ADD_SYNC, syncMsg, getRoomUserListSync(roomData.getRoomId()));
        }
    }

    /**
     * 怪物移动
     */
    public void daoMuPvpMonsterMoveTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                continue;
            }
            // 房间当前人数
            int num = getRoomUserList(roomData.getRoomId()).size();
            // 获取当前处理的池子
            long poolIndex = roomData.getHandlerMonsterMoveCnt() % 10;
            for (DaoMuPvpMonsterData monsterData : roomData.getMonsterDataMap().values()) {
                if (monsterData.getPoolIndex() != poolIndex) {
                    continue;
                }
                if (num > 0) {
                    // 有人的话配合他表演
                    monsterMoveHandler(monsterData);
                } else {
                    // 没人就不需要处理怪物了，在原地等着
                    // 解除锁定目标
                    monsterData.setLockUserId(0);
                    monsterData.getPosition().setTime(nowTime);
                }
            }
            roomData.setHandlerMonsterMoveCnt(roomData.getHandlerMonsterMoveCnt() + 1);
        }
    }

    public void daoMuPvpMonsterLockTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                continue;
            }
            // 判断下时间
            if (nowTime - roomData.getLastMonsterFindUserTime() < config.getPvpCommonConfig().getMobsHateTargetRefreshTime() * DateHelper.SECOND_MILLIONS) {
                continue;
            }
            // 怪物锁定数量
            Map<Long, Integer> lockUserCntMap = new ConcurrentHashMap<>();
            for (DaoMuPvpMonsterData monsterData : roomData.getMonsterDataMap().values()) {
                monsterLockUserHandler(monsterData, lockUserCntMap);
            }

            roomData.setLastMonsterFindUserTime(nowTime);
        }
    }

    /**
     * 房间坍塌(这里只需要检测时间坍塌，人数坍塌在进入下一层时判定)
     */
    public void roomCollapseTask() {
        long nowTime = System.currentTimeMillis();
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                // 坍塌的不处理
                continue;
            }
            DaoMuPvpConfig floorConfig = config.getPvpConfigMap().get(roomData.getFloor());
            if (floorConfig == null || floorConfig.getCollapseCondTime() <= 0) {
                continue;
            }
            if (nowTime - getBattleStartTime() >= floorConfig.getCollapseCondTime() * DateHelper.SECOND_MILLIONS) {
                // 坍塌处理
                handleCollapse(roomData.getFloor());
                // 不用再处理了
                break;
            }
        }
    }

    /**
     * 房间离线玩家检测
     */
    public void checkOffLineUserListTask() {
        long nowTime = System.currentTimeMillis();
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                // 坍塌的不处理
                continue;
            }
            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomData.getRoomId());
            if (roomUserList.isEmpty()) {
                continue;
            }
            // 离线玩家
            Set<Long> offlineUserSet = new HashSet<>();
            // 在线玩家
            Set<Long> onlineUserSet = new HashSet<>();
            for (Long userId : roomUserList) {
                if (npcSet.contains(userId)) {
                    continue;
                }
                DaoMuPvpUserData userData = getUserData(userId);
                if (userData == null) {
                    continue;
                }
                if (userData.getPingTime() == -1 || nowTime - userData.getPingTime() > 10 * DateHelper.SECOND_MILLIONS) {
                    offlineUserSet.add(userId);
                } else {
                    onlineUserSet.add(userId);
                }
            }
            // 广播
            if (offlineUserSet.size() > 0 && onlineUserSet.size() > 0) {
                DaoMuPvpProto.DaoMuPvpOffLineUserListSyncMsg.Builder syncMsg = DaoMuPvpProto.DaoMuPvpOffLineUserListSyncMsg.newBuilder();
                syncMsg.setRoomId(roomData.getRoomId());
                syncMsg.addAllUserId(offlineUserSet);
                sendPacketToAll(Protocol.U_DAOMU_PVP_OFFLINE_USER_LIST_SYNC, syncMsg, onlineUserSet);
            }
        }
    }

    /**
     * 房间缩圈
     */
    public void roomShrinkTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        Set<Long> needSyncOutUser = new HashSet<>();
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                // 坍塌的不处理
                continue;
            }
            // 没到收圈条件或不收圈
            long shrinkStartTime = roomData.getShrinkStartTime();
            if (shrinkStartTime <= 0) {
                continue;
            }
            if (nowTime < shrinkStartTime) {
                continue;
            }
            // 开始收圈
            DaoMuPvpConfig floorConfig = config.getPvpConfigMap().get(roomData.getFloor());
            if (floorConfig == null || "0".equals(floorConfig.getShrinkCircle())) {
                continue;
            }
            List<String> shrinkParamList = StringUtils.stringToStringList(floorConfig.getShrinkCircle(), "\\|");

            // 当前剩余圈的千分比
            double shrinkRadio = 1000;
            for (String param : shrinkParamList) {
                String[] split = param.split(";");
                // 收圈开始时间
                long startTime = shrinkStartTime + Integer.parseInt(split[0]) * DateHelper.SECOND_MILLIONS;
                // 收圈结束时间
                long endTime = shrinkStartTime + Integer.parseInt(split[1]) * DateHelper.SECOND_MILLIONS;
                if (nowTime >= endTime) {
                    // 大于收圈结束时间,直接算半径
                    shrinkRadio = Integer.parseInt(split[2]);
                } else if (nowTime < startTime) {
                    // 未到收圈时间,可以直接跳出
                    break;
                } else {
                    // 在缩圈阶段,算出收圈半径
                    // 根据本次收圈需要缩圈的大小算出速度
                    double totalRadio = shrinkRadio - Integer.parseInt(split[2]);
                    // 每秒缩圈的千分比
                    double speed = BigDecimal.valueOf(totalRadio).divide(BigDecimal.valueOf(Integer.parseInt(split[1]) - Integer.parseInt(split[0])), 8, RoundingMode.DOWN).doubleValue();
                    // 这一圈走了多少千分比
                    double offsetRadio = (nowTime - startTime) / 1000.0 * speed;
                    shrinkRadio = shrinkRadio - offsetRadio;
                }
            }
            logger.info("【盗墓笔记pvp】当前圈的大小,shrinkRadio:{}", shrinkRadio);
            // 计算当前半径
            double radius = BigDecimal.valueOf(config.getPvpCommonConfig().getRoomShrinkRadius()).multiply(BigDecimal.valueOf(shrinkRadio)).divide(BigDecimal.valueOf(1000), 8, RoundingMode.DOWN).doubleValue();
            // 获取当前时间造成的千分比
            List<String> shrinkTimeList = StringUtils.stringToStringList(floorConfig.getShrinkCircleHarm(), "\\|");
            // 缩圈伤害千分比
            long shrinkRadius = 0;
            long maxShrinkTime = 0;
            for (String s : shrinkTimeList) {
                String[] split = s.split(";");
                int shrinkTime = Integer.parseInt(split[0]);
                if (nowTime >= roomData.getShrinkStartTime() + shrinkTime * DateHelper.SECOND_MILLIONS && shrinkTime >= maxShrinkTime) {
                    shrinkRadius = Long.parseLong(split[1]);
                    maxShrinkTime = shrinkTime;
                }
            }
            // 计算圈外玩家受到的伤害
            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomData.getRoomId());
            for (Long userId : roomUserList) {
                DaoMuPvpUserData userData = getUserData(userId);
                if (userData == null || !userData.isLive()) {
                    continue;
                }
                // 在圈外
                if (distance(userData.getCoordinate(), ZERO_COORDINATE) >= radius) {
                    // 造成伤害
                    long hurtHp = BigDecimal.valueOf(userData.getProperty().getMaxHp()).multiply(BigDecimal.valueOf(shrinkRadius)).divide(BigDecimal.valueOf(1000), 0, RoundingMode.DOWN).longValue();
                    userData.getProperty().subHp(hurtHp);
                    logger.info("【盗墓笔记】玩家:{},受到毒圈伤害:{},当前属性,当前血量:{}", userData.getUserId(), hurtHp, userData.getProperty().getCurHp());
                    // 攻击广播（同步所有人）
                    DaoMuPvpProto.DaoMuPvpAttackedSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpAttackedSync.newBuilder();
                    DaoMuPvpProto.DaoMuPvpAttackedTemp.Builder attackedTemp = DaoMuPvpProto.DaoMuPvpAttackedTemp.newBuilder();
                    attackedTemp.setAttackedUserId(userData.getUserId());
                    attackedTemp.setHp(hurtHp);
                    attackedTemp.setAttackedHp(userData.getProperty().getCurHp());
                    attackedTemp.setAttackTime(nowTime);
                    syncMsg.setAttack(attackedTemp);
                    sendPacketToAll(Protocol.U_DAOMU_PVP_ATTACKED_SYNC, syncMsg, getRoomUserListSync(roomData.getRoomId()));

                    // 死前记录下等级
                    int logLevel = userData.getProperty().getLevel();

                    // 攻击后处理
                    if (afterAttack(userData)) {
                        if (daoMuPvpData.isDisuse()) {
                            // 淘汰处理
                            handleOut(userData, 4, nowTime);
                            needSyncOutUser.add(userData.getUserId());
                        } else {
                            // 通知被打人
                            DaoMuPvpProto.DaoMuPvpAttackedUserSync.Builder syncUserMsg = DaoMuPvpProto.DaoMuPvpAttackedUserSync.newBuilder();
                            syncUserMsg.setAttack(attackedTemp);
                            // 我的信息
                            syncUserMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
                            sendPacket(Protocol.U_DAOMU_PVP_ATTACKED_USER_SYNC, syncUserMsg, userData.getUserId());
                        }

                        if (!npcSet.contains(userId)) {
                            CrossAutoLogMgr.add(new LogDaoMuPvpKill(
                                    config.getActivityId(),
                                    daoMuPvpData.getRound(),
                                    userId,
                                    logLevel,
                                    3,
                                    0, 0, 0,
                                    logLevel - userData.getProperty().getLevel(),
                                    nowTime
                            ));
                        }
                    }
                }
            }
        }
        // 通知下缩圈淘汰
        for (Long userId : needSyncOutUser) {
            DaoMuPvpUserData userData = getUserData(userId);
            if (userData == null) {
                continue;
            }
            // 同步下
            DaoMuPvpProto.DaoMuPvpOutSyncMsg.Builder outSyncMsg = DaoMuPvpProto.DaoMuPvpOutSyncMsg.newBuilder();
            outSyncMsg.setType(userData.getOutType());
            outSyncMsg.setSettle(parseDaoMuPvpSettleTemp(userData));
            sendPacket(Protocol.U_DAOMU_PVP_OUT_SYNC, outSyncMsg, userData.getUserId());
        }
    }


    /**
     * 校验活动时间
     */
    public void checkActivityTimeTask() {
        long nowTime = System.currentTimeMillis();
        // 判断是否切换轮次
        int curRound = getCurRound();
        if (curRound != daoMuPvpData.getRound()) {
            // 切换轮次
            changeRound();
        }

        // 判断是否达到锁定时间
        if (isBattleTime()) {
            if (!daoMuPvpData.isLockState()) {
                // 开始锁定
                daoMuPvpData.setLockState(true);

                // 分配房间
                int rankNum = 0;
                for (DaoMuRank rank : applyRankListModel.getRankList()) {
                    rankNum++;
                    if (rankNum <= config.getPvpCommonConfig().getNumLimit()) {
                        // 添加资格，分配出生点
                        allotUser(rank.getUserId());
                    }
                    // 处理下用户数据
                    DaoMuPvpUserData userData = getUserData(rank.getUserId());
                    if (userData == null) {
                        continue;
                    }
                    // 记录下报名日志
                    CrossAutoLogMgr.add(new LogDaoMuPvpApply(
                            config.getActivityId(),
                            daoMuPvpData.getRound(),
                            rank.getUserId(),
                            userData.getCollectionLevel(),
                            userData.getCollectionScore(),
                            userData.isAble(),
                            rankNum,
                            userData.getPveUploadTime()
                    ));

                }
            }

            // 检测坍塌时间公告
            for (DaoMuPvpConfig pvpConfig : config.getPvpConfigMap().values()) {
                if (pvpConfig.getCollapseCondTime() == 0) {
                    continue;
                }
                int floor = pvpConfig.getId();
                // 判断本层是否已坍塌
                if (daoMuPvpData.getCollapseFloors().contains(floor)) {
                    continue;
                }
                if (!config.getPvpCommonConfig().getNoticeCollapseTime().containsKey(floor)) {
                    continue;
                }
                // 当前时间
                long curSecond = ((nowTime - getBattleStartTime()) / 1000);
                int leftCollapseTime = (int) (pvpConfig.getCollapseCondTime() - curSecond);
                if (leftCollapseTime >= 0 && config.getPvpCommonConfig().getNoticeCollapseTime().get(floor).contains(leftCollapseTime)) {
                    addNotice(0, 0, DaoMuPvpNoticeType.COLLAPSE_BY_TIME.getType(), floor + "#" + leftCollapseTime);
                }
            }
        }
    }

    /**
     * 切换轮次
     */
    private void changeRound() {
        int curRound = getCurRound();
        daoMuPvpData.setRound(curRound);
        daoMuPvpData.setLockState(false);
        daoMuPvpData.setFloorCntMap(new ConcurrentHashMap<>());
        daoMuPvpData.setDisuse(false);
        daoMuPvpData.setSettle(false);
        daoMuPvpData.setCollapseFloors(new HashSet<>());

        // 重置房间信息
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            // 保险一点再清一下
            roomData.clearData();
            roomData.setCollapse(false);
            roomData.setShrinkStartTime(0);
            // 初始化下房间数据
            initRoomData(roomData);
        }
        // 重置玩家信息
        for (DaoMuPvpUserData userData : userDataMap.values()) {
            userData.setRoomId(0);
            userData.setApply(false);
            userData.setAble(false);
            userData.setOut(false);
            userData.setOutTime(0);
            userData.setOutType(0);
            // 属性重置
            userData.setProperty(getInitProperty());
            // 位置重置
            userData.setPosition(new DaoMuPvpPosition());
            // 清空内存的武器
            userData.setBulletDataList(new ArrayList<>());
        }

        // 重置榜单相关
        applyRankListModel = new DaoMuRankListModel();
        aliveRankListModel = new DaoMuRankListModel(new DaoMuAliveRankSort());
        aliveCnt = new AtomicInteger(0);
        // 内存房间用户
        roomUserMap = new ConcurrentHashMap<>();
        // 重置公告
        noticeDataList = new LinkedList<>();
    }

    /**
     * 更新用户的基础属性
     */
    private void calcUserBaseAttribute(DaoMuPvpUserData userData) {
        if (userData == null) {
            return;
        }
        // 更新属性初始属性
        DaoMuCaptainConfig captainConfig = config.getCaptainConfigMap().get(userData.getCollectionLevel());
        if (captainConfig == null) {
            return;
        }
        DaoMuPvpCommonConfig pvpCommonConfig = config.getPvpCommonConfig();
        // 基础属性
        List<Long> pvpBaseParamList = StringUtils.stringToLongList(captainConfig.getPvpBasePara(), "\\|");
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_HP.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_HP, 0L) + pvpBaseParamList.get(0));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_ATT.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_ATT, 0L) + pvpBaseParamList.get(1));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_DEFEND.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_DEFEND, 0L) + pvpBaseParamList.get(2));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_RECOVER_HP_PVP.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_RECOVER_HP_PVP, 0L) + pvpBaseParamList.get(3));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_SPEED.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_SPEED, 0L) + pvpBaseParamList.get(4));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_PICKING_DISTANCE.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_PICKING_DISTANCE, 0L) + pvpBaseParamList.get(5));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_BULLET_LIMIT.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_BULLET_LIMIT, 0L) + pvpBaseParamList.get(6));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_BULLET_ROTATIONAL_SPEED.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_BULLET_ROTATIONAL_SPEED, 0L) + pvpBaseParamList.get(7));
        userData.getProperty().getBaseAttributeMap().put(ATTRIBUTE_BULLET_RECOVER.getType(), pvpCommonConfig.getInitAttributeMap().getOrDefault(ATTRIBUTE_BULLET_RECOVER, 0L) + pvpBaseParamList.get(8));

        // 处理下上限
        for (Map.Entry<DaoMuAttributeEnum, Long> entry : config.getPvpCommonConfig().getMaxAttributeMap().entrySet()) {
            DaoMuAttributeEnum attributeEnum = entry.getKey();
            Long maxValue = entry.getValue();
            if (!userData.getProperty().getBaseAttributeMap().containsKey(attributeEnum.getType())) {
                continue;
            }
            userData.getProperty().getBaseAttributeMap().put(attributeEnum.getType(), Math.min(maxValue, userData.getProperty().getBaseAttributeMap().getOrDefault(attributeEnum.getType(), 0L)));
        }

        // 更新战斗属性
        changeAttributeMap(userData);
    }

    /**
     * 初始化分配
     *
     * @param userId
     */
    private void allotUser(long userId) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return;
        }
        if (userData.getCollectionLevel() == 0 || userData.getCollectionScore() == 0) {
            // 没属性，别玩了
            return;
        }
        // 添加资格
        userData.setAble(true);
        // 重新计算用户属性
        calcUserBaseAttribute(userData);
        // 初始化血量
        userData.getProperty().setCurHp(userData.getProperty().getMaxHp());
        DaoMuPlayerLevelConfig levelConfig = config.getDaoMuLevelConfigMap().get(userData.getProperty().getLevel());
        if (levelConfig != null) {
            // 创建初始武器
            createBullet(userData, levelConfig.getPvpInitialArmsNum());
        }

        // 分配房间
        int initFloor = 1;
        DaoMuPvpBirthData initBirthData = randomBirthInFloor(initFloor);
        if (initBirthData == null) {
            // 没房间了
            return;
        }
        // 加入房间
        joinRoom(initBirthData.getRoomId(), userData, initBirthData);
        // 计数
        addFloorCnt(initFloor);
        // 记录存活人数
        aliveCnt.incrementAndGet();
        try {
            logger.info("allotUser,activityId:{},userId:{},roomId:{},floor:{}", config.getActivityId(), userId, initBirthData.getRoomId(), initFloor);
        } catch (Exception exception) {
            logger.error("allotUser,exception:", exception);
        }
    }

    /**
     * 计算npc位置
     */
    public void npcMoveHandler(DaoMuPvpUserData userData) {
        if (userData == null || !userData.isLive()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        // 拷贝一个出来计算
        DaoMuPvpPosition position = userData.getPosition().clone();
        // 每毫秒的移动速度
        double speed = userData.getProperty().getAttributeMap().get(ATTRIBUTE_SPEED.getType()) * 1.0 / 1000;
        double offsetTime = nowTime - position.getTime();
        double oldX = position.getX();
        double dx = position.getDirX();
        double offsetX = dx * speed * offsetTime;
        position.setX(calcX( oldX + offsetX));
        double oldY = position.getY();
        double dy = position.getDirY();
        double offsetY = dy * speed * offsetTime;
        position.setY(calcY( oldY + offsetY));
        position.setTime(nowTime);
        // 处理移动
        playerMoveHandler(userData, position, true);
    }

    /**
     * 计算x轴坐标
     */
    public double calcX(double x) {
        return Math.max(roomMinX(), Math.min(roomMaxX(), x));
    }

    /**
     * 计算x轴坐标
     */
    public double calcGoodsX(double x) {
        return Math.max(roomMinX() + config.getPvpCommonConfig().getRoomSideSize(), Math.min(roomMaxX() - config.getPvpCommonConfig().getRoomSideSize(), x));
    }


    /**
     * 计算y轴坐标
     */
    public double calcY(double y) {
        return Math.max(roomMinY(), Math.min(roomMaxY(), y));
    }

    /**
     * 计算y轴坐标
     */
    public double calcGoodsY(double y) {
        return Math.max(roomMinY() + config.getPvpCommonConfig().getRoomSideSize(), Math.min(roomMaxY() - config.getPvpCommonConfig().getRoomSideSize(), y));
    }
    /**
     * 怪物寻找锁定目标
     *
     * @param monsterData
     * @param lockUserCntMap
     */
    public void monsterLockUserHandler(DaoMuPvpMonsterData monsterData, Map<Long, Integer> lockUserCntMap) {
        if (monsterData == null || !monsterData.isLive()) {
            return;
        }
        // 清空下目标
        monsterData.setLockUserId(0);
        long minDistanceUserId = findMinDistanceUserId(monsterData, lockUserCntMap);
        if (minDistanceUserId != 0) {
            monsterData.setLockUserId(minDistanceUserId);
            lockUserCntMap.put(minDistanceUserId, lockUserCntMap.getOrDefault(minDistanceUserId, 0) + 1);
        }
    }

    /**
     * 计算怪物位置
     */
    public void monsterMoveHandler(DaoMuPvpMonsterData monsterData) {
        if (monsterData == null || !monsterData.isLive()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        // 更新位置
        monsterData.updatePosition();
        // 处理下怪物边界
        monsterData.getPosition().setX(calcX(monsterData.getPosition().getX()));
        monsterData.getPosition().setY(calcX(monsterData.getPosition().getY()));
        monsterData.getPosition().setTime(nowTime);

        // 更新下npc的方向
        Coordinate direction = null;
        if (monsterData.getLockUserId() != 0) {
            DaoMuPvpUserData minDistanceUser = getUserData(monsterData.getLockUserId());
            if (minDistanceUser != null) {
                direction = calcDirection(monsterData.getCoordinate(), minDistanceUser.getCoordinate());
            }
        } else {
            // 没有目标的间隔10s再换方向
            if (nowTime - monsterData.getChangeDirTime() >= 10 * DateHelper.SECOND_MILLIONS) {
                direction = randomDir();
            }
        }
        // 检测下边界位置
        if (monsterData.getPosition().getX() <= roomMinX() || monsterData.getPosition().getX() >= roomMaxX()
                || monsterData.getPosition().getY() <= roomMinY() || monsterData.getPosition().getY() >= roomMaxY()) {
            direction = randomDir();
        }

        if (direction != null) {
            monsterData.getPosition().setDirX(direction.getX());
            monsterData.getPosition().setDirY(direction.getY());
            monsterData.setChangeDirTime(nowTime);
        }
        // 广播
        addMonsterPosition(monsterData.getRoomId(), monsterData.getMonsterId(), monsterData.getPosition());
    }

    public double roomMinX() {
        return -config.getPvpCommonConfig().getRoomMaxWidth() / 2;
    }

    public double roomMaxX() {
        return config.getPvpCommonConfig().getRoomMaxWidth() / 2;
    }

    public double roomMinY() {
        return -config.getPvpCommonConfig().getRoomMaxHeight() / 2;
    }

    public double roomMaxY() {
        return config.getPvpCommonConfig().getRoomMaxHeight() / 2;
    }

    /**
     * 定时创建子弹
     */
    public void createBulletTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        for (Integer roomId : roomDataMap.keySet()) {
            // 获取房间内的玩家
            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomId);
            for (Long userId : roomUserList) {
                DaoMuPvpUserData userData = getUserData(userId);
                if (userData == null || !userData.isLive()) {
                    continue;
                }
                DaoMuPvpProperty property = userData.getProperty();
                // 我方子弹
                if (userData.getBulletDataList().size() >= property.getAttributeMap().get(ATTRIBUTE_BULLET_LIMIT.getType())) {
                    // 子弹大于上限
                    continue;
                }
                // 计算恢复间隔
                long offsetTime = BigDecimal.valueOf(1000).divide(BigDecimal.valueOf(property.getAttributeMap().get(ATTRIBUTE_BULLET_RECOVER.getType())), 8, RoundingMode.DOWN).multiply(BigDecimal.valueOf(1000)).setScale(0, RoundingMode.DOWN).longValue();
                if (nowTime - property.getLastBulletRecoverTime() < offsetTime) {
                    // 没到恢复时间
                    continue;
                }
                // 增加子弹
                createBullet(userData, 1);
            }
        }
    }

    public void refreshBulletList(DaoMuPvpUserData userData) {
        long nowTime = System.currentTimeMillis();
        // 更新下子弹的初始角度及速度
        double offsetAngle = 360.0 / userData.getBulletDataList().size();
        double currentAngle = 0;

        // 子弹转速
        double rotationalSpeed = userData.getProperty().getAttributeMap().get(ATTRIBUTE_BULLET_ROTATIONAL_SPEED.getType()) / 1000.0;

        for (DaoMuPvpBulletData data : userData.getBulletDataList()) {
            data.setInitialAngle(currentAngle);
            // 下一个的角度
            currentAngle += offsetAngle;
            // 更新转速
            data.setSpeed(rotationalSpeed);
            data.setCreateTime(nowTime);
        }
        // 记录下恢复时间
        userData.getProperty().setLastBulletRecoverTime(nowTime);
    }

    /**
     * 检测碰撞
     */
    public void checkBulletCollisionTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        for (Integer roomId : roomDataMap.keySet()) {
            // 获取房间内的玩家
            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomId);
            for (Long userId : roomUserList) {
                DaoMuPvpUserData userData = getUserData(userId);
                if (userData == null || !userData.isLive()) {
                    continue;
                }
                if (!userData.isProtectTime()) {
                    // 检验玩家碰撞
                    checkPlayerCollision(userData);
                }
                // 校验宝箱碰撞
                checkBoxCollision(userData);
            }
        }
    }

    /**
     * 检测碰撞
     */
    public void checkMonsterCollisionTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData == null || roomData.isCollapse()) {
                continue;
            }
            for (DaoMuPvpMonsterData monsterData : roomData.getMonsterDataMap().values()) {
                // 检测碰撞
                checkMonsterCollision(roomData.getRoomId(), monsterData);
            }
        }

    }

    /**
     * 检测怪物和人物的碰撞
     */
    private void checkMonsterCollision(int roomId, DaoMuPvpMonsterData monsterData) {
        if (monsterData == null || !monsterData.isLive() || monsterData.getLockUserId() == 0) {
            return;
        }
        // 更新下怪物的方向
        monsterData.updatePosition();
        // 处理下怪物边界
        monsterData.getPosition().setX(calcX(monsterData.getPosition().getX()));
        monsterData.getPosition().setY(calcX(monsterData.getPosition().getY()));

        long nowTime = System.currentTimeMillis();
        if (nowTime - monsterData.getAttackTime() < DateHelper.SECOND_MILLIONS) {
            return;
        }
        DaoMuPvpUserData userData = getUserData(monsterData.getLockUserId());
        if (userData == null || !userData.isLive() || userData.getRoomId() != roomId) {
            return;
        }
        DaoMuNpcConfig npcConfig = config.getDaoMuNpcConfigMap().get(monsterData.getFaceId());
        if (npcConfig == null) {
            return;
        }
        // 怪物碰撞区域
        String[] collisionSizes = npcConfig.getCollisionSize().split(";");
        double monsterWidth = Double.parseDouble(collisionSizes[0]);
        double monsterHeight = Double.parseDouble(collisionSizes[1]);
        double userWidth = ROLE_WIDTH;
        double userHeight = ROLE_HEIGHT;

        // 获取怪物矩形的四个角的点坐标
        List<Coordinate> points1 = new ArrayList<>();
        points1.add(new Coordinate(monsterData.getCoordinate().getX() - monsterWidth / 2.0, monsterData.getCoordinate().getY() + monsterHeight / 2.0)); // 左上
        points1.add(new Coordinate(monsterData.getCoordinate().getX() + monsterWidth / 2.0, monsterData.getCoordinate().getY() + monsterHeight / 2.0)); // 右上
        points1.add(new Coordinate(monsterData.getCoordinate().getX() + monsterWidth / 2.0, monsterData.getCoordinate().getY() - monsterHeight / 2.0)); // 右下
        points1.add(new Coordinate(monsterData.getCoordinate().getX() - monsterWidth / 2.0, monsterData.getCoordinate().getY() - monsterHeight / 2.0)); // 左下

        // 获取人物矩形的四个角的点坐标
        List<Coordinate> points2 = new ArrayList<>();
        points2.add(new Coordinate(userData.getCoordinate().getX() - userWidth / 2.0, userData.getCoordinate().getY() + userHeight / 2.0)); // 左上
        points2.add(new Coordinate(userData.getCoordinate().getX() + userWidth / 2.0, userData.getCoordinate().getY() + userHeight / 2.0)); // 右上
        points2.add(new Coordinate(userData.getCoordinate().getX() + userWidth / 2.0, userData.getCoordinate().getY() - userHeight / 2.0)); // 右下
        points2.add(new Coordinate(userData.getCoordinate().getX() - userWidth / 2.0, userData.getCoordinate().getY() - userHeight / 2.0)); // 左下

        // 判断碰撞
        if (CollisionHelper.intersectsWithRectangle(points1, points2)) {
            // 碰撞后判断能否造成伤害，0.5s内再次碰撞可造成伤害，如果无碰撞或者超出0.5s重新计时
            if (nowTime - monsterData.getCollisionTime() > 500) {
                monsterData.setCollisionTime(nowTime);
                return;
            } else {
                // 造成伤害,往下走
                monsterData.setCollisionTime(nowTime);
                monsterData.setAttackTime(nowTime);
            }

            // 造成伤害
            // 获取怪物的攻击
            long hurtHp = Math.max(1, monsterData.getAttack() - userData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_DEFEND.getType(), 0L));
            userData.getProperty().subHp(hurtHp);
            logger.info("【盗墓笔记】玩家:{},受到怪物伤害:{},当前属性,当前血量:{}", userData.getUserId(), hurtHp, userData.getProperty().getCurHp());

            // 攻击广播（同步所有人）
            DaoMuPvpProto.DaoMuPvpAttackedSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpAttackedSync.newBuilder();
            DaoMuPvpProto.DaoMuPvpAttackedTemp.Builder attackedTemp = DaoMuPvpProto.DaoMuPvpAttackedTemp.newBuilder();
            attackedTemp.setAttackedUserId(userData.getUserId());
            attackedTemp.setHp(hurtHp);
            attackedTemp.setAttackedHp(userData.getProperty().getCurHp());
            attackedTemp.setAttackTime(nowTime);
            syncMsg.setAttack(attackedTemp);
            sendPacketToAll(Protocol.U_DAOMU_PVP_ATTACKED_SYNC, syncMsg, getRoomUserListSync(roomId));

            int logLevel = userData.getProperty().getLevel();

            // 攻击后处理
            if (afterAttack(userData)) {

                // 记录击杀的怪物id
                userData.getProperty().getBattleRecord().setKillMonsterId(monsterData.getFaceId());

                if (daoMuPvpData.isDisuse()) {
                    // 淘汰处理
                    handleOut(userData, 3, nowTime);

                    // 同步下
                    DaoMuPvpProto.DaoMuPvpOutSyncMsg.Builder outSyncMsg = DaoMuPvpProto.DaoMuPvpOutSyncMsg.newBuilder();
                    outSyncMsg.setType(userData.getOutType());
                    outSyncMsg.setMonsterId(monsterData.getFaceId());
                    outSyncMsg.setSettle(parseDaoMuPvpSettleTemp(userData));
                    sendPacket(Protocol.U_DAOMU_PVP_OUT_SYNC, outSyncMsg, userData.getUserId());
                } else {
                    // 通知被打人
                    DaoMuPvpProto.DaoMuPvpAttackedUserSync.Builder syncUserMsg = DaoMuPvpProto.DaoMuPvpAttackedUserSync.newBuilder();
                    syncUserMsg.setAttack(attackedTemp);
                    // 我的信息
                    syncUserMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
                    sendPacket(Protocol.U_DAOMU_PVP_ATTACKED_USER_SYNC, syncUserMsg, userData.getUserId());
                }
                if (!npcSet.contains(userData.getUserId())) {
                    CrossAutoLogMgr.add(new LogDaoMuPvpKill(
                            config.getActivityId(),
                            daoMuPvpData.getRound(),
                            userData.getUserId(),
                            logLevel,
                            2,
                            0, monsterData.getFaceId(), 0,
                            logLevel - userData.getProperty().getLevel(),
                            nowTime
                    ));
                }
            }
        }
    }

    private void checkBoxCollision(DaoMuPvpUserData userData) {
        if (userData == null) {
            return;
        }
        int roomId = userData.getRoomId();
        // 房间信息
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData == null) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        // 我方子弹
        List<DaoMuPvpBulletData> myBulletList = userData.getBulletDataList();
        if (myBulletList == null || myBulletList.isEmpty()) {
            // 没子弹，不校验了，让别人打去吧
            return;
        }
        // 获取用户攻击范围内的宝箱
        List<DaoMuPvpBoxData> distanceBoxDataList = findDistanceBoxData(userData, 300);
        if (distanceBoxDataList.isEmpty()) {
            return;
        }
        for (DaoMuPvpBulletData myBullet : myBulletList) {
            if (myBullet == null) {
                continue;
            }
            // 检测碰撞
            for (DaoMuPvpBoxData boxData : distanceBoxDataList) {
                if (boxData == null || !boxData.isLive()) {
                    continue;
                }
                if (myBullet.intersectsWithRectangle(userData.getCoordinate(), boxData.getCoordinate(), BOX_WIDTH, BOX_HEIGHT, nowTime)) {
                    // 碰到宝箱,判断下子弹能不能打到
                    if (!myBullet.getAttackTimeMap().containsKey(boxData.getUid()) || nowTime - myBullet.getAttackTimeMap().getOrDefault(boxData.getUid(), 0L) > DateHelper.SECOND_MILLIONS) {
                        // 受击次数+1
                        boxData.setAttackCnt(boxData.getAttackCnt() - 1);
                        // 添加受击间隔
                        myBullet.getAttackTimeMap().put(boxData.getUid(), nowTime);

                        // 广播下宝箱受击
                        notifyMonsterAttack(roomId, 1, boxData.getUid(), boxData.getAttackCnt());

                        // 检测宝箱是否消失
                        if (!boxData.isLive()) {
                            // 销毁宝箱
                            removeBoxItem(roomId, boxData.getUid());

//                            logger.info("【盗墓笔记】宝箱销毁,宝箱id:{},攻击者:{},位置,x:{},y:{},掉落:{},下次刷新时间:{}", boxData.getUid(), userData.getUserId(), boxData.getCoordinate().getX(), boxData.getCoordinate().getY(), JSON.toJSONString(boxData.getGoodsList()), boxData.getNextRefreshTime());
                            // 添加物资
                            List<DaoMuPvpGoodsData> goodsDataList = new ArrayList<>();
                            for (Integer goodsId : boxData.getGoodsList()) {
                                DaoMuPvpGoodsData goodsData = new DaoMuPvpGoodsData();
                                goodsData.setUid(randomUUID());
                                goodsData.setGoodsId(goodsId);
                                Coordinate coordinate = generateRandomCoordinate(boxData.getCoordinate().getX(), boxData.getCoordinate().getY(), config.getPvpCommonConfig().getBoxDropRadius());
                                // 边界处理下
                                coordinate.setX(calcGoodsX(coordinate.getX()));
                                coordinate.setY(calcGoodsY(coordinate.getY()));
                                goodsData.setCoordinate(coordinate);
                                goodsData.setExpiredTime(System.currentTimeMillis() + config.getPvpCommonConfig().getGoodsDisappearTime() * DateHelper.SECOND_MILLIONS);
                                goodsDataList.add(goodsData);
                            }
                            addRoomGoodsItem(roomId, goodsDataList);
                        }
                    }
                }
            }
        }

    }

    /**
     * 广播宝箱、怪物受击
     */
    public void notifyMonsterAttack(int roomId, int type, String id, int attackCnt) {
        DaoMuPvpProto.DaoMuPvpMonsterAttackSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpMonsterAttackSync.newBuilder();
        syncMsg.setRoomId(roomId);
        syncMsg.setType(type);
        syncMsg.setId(id);
        syncMsg.setAttackCnt(attackCnt);
        sendPacketToAll(Protocol.U_DAOMU_PVP_MONSTER_ATTACK_SYNC, syncMsg, getRoomUserListSync(roomId));
    }

    /**
     * 检验玩家碰撞
     *
     * @param userData
     */
    public void checkPlayerCollision(DaoMuPvpUserData userData) {
        if (userData == null) {
            return;
        }
        int roomId = userData.getRoomId();
        long nowTime = System.currentTimeMillis();
        List<DaoMuPvpBulletData> myBulletList = userData.getBulletDataList();
        if (myBulletList == null || myBulletList.isEmpty()) {
            return;
        }
        List<DaoMuPvpUserData> distanceUserDataList = findDistanceUserData(userData, 350);
        if (distanceUserDataList.isEmpty()) {
            return;
        }
        // 将Map值类型修改为Long
        Map<String, Long> needDestroyBulletMap = new HashMap<>();
        for (DaoMuPvpUserData targetUserData : distanceUserDataList) {
            if (targetUserData == null || !targetUserData.isLive() || targetUserData.isProtectTime()) {
                continue;
            }

            List<DaoMuPvpBulletData> targetBulletList = targetUserData.getBulletDataList();

            for (DaoMuPvpBulletData myBullet : myBulletList) {
                String myBulletId = myBullet.getBulletId();
                if (needDestroyBulletMap.containsKey(myBulletId)) {
                    continue;
                }

                for (DaoMuPvpBulletData targetBullet : targetBulletList) {
                    String targetBulletId = targetBullet.getBulletId();
                    if (needDestroyBulletMap.containsKey(myBulletId) || needDestroyBulletMap.containsKey(targetBulletId)) {
                        continue;
                    }

                    if (myBullet.intersectsWithRectangle(userData.getCoordinate(), targetBullet, targetUserData.getCoordinate(), nowTime)) {
                        userData.destroyBulletData(myBulletId);
                        needDestroyBulletMap.put(myBulletId, myBullet.getUserId());

                        targetUserData.destroyBulletData(targetBulletId);
                        needDestroyBulletMap.put(targetBulletId, targetBullet.getUserId());

//                        if (!npcSet.contains(myBullet.getUserId())) {
//                            logger.info("【碰刀】id1 :{},id2:{}", myBulletId, targetBulletId);
//                        }
                        break;
                    }
                }

                if (!needDestroyBulletMap.containsKey(myBulletId) && myBullet.intersectsWithRectangle(userData.getCoordinate(), targetUserData.getCoordinate(), ROLE_WIDTH, ROLE_HEIGHT, nowTime)) {
                    if (!myBullet.getAttackTimeMap().containsKey(String.valueOf(targetUserData.getUserId())) || nowTime - myBullet.getAttackTimeMap().getOrDefault(String.valueOf(targetUserData.getUserId()), 0L) > DateHelper.SECOND_MILLIONS) {
                        attack(roomId, myBullet.getUserId(), targetUserData.getUserId());
                        myBullet.getAttackTimeMap().put(String.valueOf(targetUserData.getUserId()), nowTime);
                    }
                }
            }
        }
        notifyDestroyBullet(roomId, needDestroyBulletMap);
    }

    public void notifyDestroyBullet(int roomId, Map<String, Long> map) {
        if (map.isEmpty()) {
            return;
        }
        // 同步下
        DaoMuPvpProto.DaoMuPvpDestroyBulletSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpDestroyBulletSync.newBuilder();
        for (Map.Entry<String, Long> entry : map.entrySet()) {
            DaoMuPvpProto.DaoMuPvpBulletSimpleTemp.Builder temp = DaoMuPvpProto.DaoMuPvpBulletSimpleTemp.newBuilder();
            temp.setBulletId(entry.getKey());
            temp.setUserId(entry.getValue());
            syncMsg.addBullet(temp);
        }
        sendPacketToAll(Protocol.U_DAOMU_PVP_DESTROY_BULLET_SYNC, syncMsg, getRoomUserListSync(roomId));
    }

    /**
     * 复活
     *
     * @param userId
     */
    public int reviveByCmd(long userId) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        if (!userData.isLive()) {
            // 处理复活
            if (userData.isOut()) {
                return GameErrorCode.E_DAOMU_PVP_IS_OUT;
            }
            // 冷却时间
            if (System.currentTimeMillis() < userData.getProperty().getReviveTime()) {
                return GameErrorCode.E_DAOMU_PVP_ROOM_TIME_NOT_ENOUGH;
            }
            // 处理复活
            handleRevive(userData);
        }
        // 返回操作
        DaoMuPvpProto.DaoMuPvpOperateRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpOperateRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setType(3);
        respMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
        sendPacket(Protocol.U_DAOMU_PVP_OPERATE, respMsg, userId);
        return 0;
    }

    /**
     * 捡物资
     *
     * @param userId 用户id
     * @param time   操作时间
     */
    public int getRoomGoodsByCmd(long userId, String uid, long time) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        if (!userData.isLive()) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_USER_IS_DIE;
        }
        if (userData.getRoomId() == 0) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_DATA_NOT_FOUND;
        }
        DaoMuPvpRoomData roomData = getRoomData(userData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_DATA_NOT_FOUND;
        }
        DaoMuPvpGoodsData goodsData;
        synchronized (getRoomLock(userData.getRoomId())) {
            goodsData = roomData.getGoodsDataMap().get(uid);
            if (goodsData == null) {
                return GameErrorCode.E_DAOMU_PVP_ROOM_ITEM_NOT_FOUND;
            }
            // 判断下距离   50像素的容错
            if (distance(userData.getCoordinate(), goodsData.getCoordinate()) > userData.getProperty().getAttributeMap().get(ATTRIBUTE_PICKING_DISTANCE.getType()) + 1000) {
                return GameErrorCode.E_DAOMU_PVP_DISTANCE_LIMIT;
            }
            // 更新下
            removeRoomGoods(userData.getRoomId(), uid);
        }
        // 处理下局内物资
        handlerRoomGoods(userData, goodsData);
        return 0;
    }

    /**
     * 开技能
     */
    public int openSkillByCmd(long userId) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        if (!userData.getProperty().getChooseSkillList().isEmpty()) {
            return GameErrorCode.E_DAOMU_PVP_HAS_SKILL_NO_CHOOSE;
        }
        if (userData.getProperty().getLearnSkillCnt() <= 0) {
            return GameErrorCode.E_DAOMU_PVP_SKILL_CHOOSE_NOT_TIMES;
        }
        userData.getProperty().setLearnSkillCnt(userData.getProperty().getLearnSkillCnt() - 1);

        List<Integer> notIncludeSkillId = new ArrayList<>();
        // 排除已满级的技能
        for (DaoMuPvpSkillData skillData : userData.getProperty().getSkillDataList()) {
            if (config.getDaoMuPvpSkillConfig(skillData.getSkillId(), skillData.getLevel() + 1) == null) {
                notIncludeSkillId.add(skillData.getSkillId());
            }
        }
        for (int i = 0; i < 3; i++) {
            NormalWeightItem randomResult = WeightHelper.getRandomResult(config.getPvpCommonConfig().getSkillPoolWeights(), notIncludeSkillId);
            if (randomResult != null) {
                int skillId = randomResult.getValue().intValue();
                notIncludeSkillId.add(skillId);
                userData.getProperty().getChooseSkillList().add(skillId);
            }
        }

        userData.setUpdateOption();

        // 返回操作
        DaoMuPvpProto.DaoMuPvpOperateRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpOperateRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setType(5);
        respMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
        sendPacket(Protocol.U_DAOMU_PVP_OPERATE, respMsg, userId);
        return 0;
    }

    /**
     * 获取当前房间内的存活宝箱
     */
    public int getRoomBoxUidByCmd(long userId) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpRoomData roomData = getRoomData(userData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_DATA_NOT_FOUND;
        }

        // 返回操作
        DaoMuPvpProto.DaoMuPvpOperateRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpOperateRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setType(8);
        for (DaoMuPvpBoxData boxData : roomData.getBoxDataList()) {
            if (boxData != null && boxData.isLive()) {
                respMsg.addBoxUid(boxData.getUid());
            }
        }
        sendPacket(Protocol.U_DAOMU_PVP_OPERATE, respMsg, userId);
        return 0;
    }


    /**
     * 选技能
     */
    public int chooseSkillByCmd(long userId, int skillId) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        if (!userData.getProperty().getChooseSkillList().contains(skillId)) {
            return GameErrorCode.E_DAOMU_PVP_SKILL_NOT_FOUND;
        }



        // 添加技能
        userData.getProperty().addSkill(skillId);
        // 清空技能
        userData.getProperty().setChooseSkillList(new ArrayList<>());

        // 计算属性
        changeAttributeMap(userData);


        userData.setUpdateOption();

        // 返回操作
        DaoMuPvpProto.DaoMuPvpOperateRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpOperateRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setType(6);
        respMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
        sendPacket(Protocol.U_DAOMU_PVP_OPERATE, respMsg, userId);
        return 0;
    }

    /**
     * 处理局内道具
     */
    private void handlerRoomGoods(DaoMuPvpUserData userData, DaoMuPvpGoodsData goodsData) {
        if (userData == null || goodsData == null) {
            return;
        }
        DaoMuCurioConfig curioConfig = config.getDaoMuCurioConfigMap().get(goodsData.getGoodsId());
        if (curioConfig == null) {
            return;
        }
        if (curioConfig.getType() == DaoMuCurioTypeEnum.TYPE_EXP.getType()) {
            int oldLevel = userData.getProperty().getLevel();
            // 增加经验
            addExp(userData, Long.parseLong(curioConfig.getAttributePara()));
            // 判断下是否广播, 升级才广播
            notifyUserProperty(userData, userData.getProperty().getLevel() != oldLevel);
        } else if (curioConfig.getType() == DaoMuCurioTypeEnum.TYPE_STOP_BLEED.getType()) {
            // 回血散,回血
            long addHp = BigDecimal.valueOf(userData.getProperty().getAttributeMap().get(ATTRIBUTE_HP.getType())).multiply(BigDecimal.valueOf(Long.parseLong(curioConfig.getAttributePara()))).divide(BigDecimal.valueOf(1000), 0, RoundingMode.DOWN).longValue();
            userData.getProperty().addHp(addHp);
            logger.info("【盗墓笔记】玩家:{},吃回血道具,回复血量,当前血量:{}", userData.getUserId(), userData.getProperty().getCurHp());

            // 属性广播下
            notifyUserProperty(userData, true);
        } else if (curioConfig.getType() == DaoMuCurioTypeEnum.TYPE_PVP_ATTACK_BUFF.getType()) {
            // 攻击buff
            List<Long> buffParams = StringUtils.stringToLongList(curioConfig.getAttributePara(), "\\|");
            userData.getProperty().addBuff(DaoMuPvpBuffType.Attack.getBuffType(), buffParams.get(0), buffParams.get(1));

            // 重新计算属性
            changeAttributeMap(userData);
        } else if (curioConfig.getType() == DaoMuCurioTypeEnum.TYPE_PVP_SPEED_BUFF.getType()) {
            //  移速buff
            List<Long> buffParams = StringUtils.stringToLongList(curioConfig.getAttributePara(), "\\|");
            userData.getProperty().addBuff(DaoMuPvpBuffType.Speed.getBuffType(), buffParams.get(0), buffParams.get(1));

            // 重新计算属性
            changeAttributeMap(userData);
        } else if (curioConfig.getType() == DaoMuCurioTypeEnum.TYPE_PVP_CUSTOM.getType()) {
            // pvp自定义类型
            if (goodsData.getGoodsId() == PVP_DROP_EXP_ID) {
                int oldLevel = userData.getProperty().getLevel();
                // 增加经验
                addExp(userData, Long.parseLong(goodsData.getExtraData()));
                // 判断下是否广播, 升级才广播
                notifyUserProperty(userData, userData.getProperty().getLevel() != oldLevel);
            } else if (goodsData.getGoodsId() == PVP_DROP_BULLET_ID) {
                // 加武器
                createBullet(userData, Integer.parseInt(goodsData.getExtraData()));
            }
        } else {
            // 其他类型不需处理，非pvp道具
        }
        // 记录下日志 (除了怪物掉落的经验石)
        if (curioConfig.getType() != DaoMuCurioTypeEnum.TYPE_EXP.getType()) {
            CrossAutoLogMgr.add(new LogDaoMuPvpPick(
                    config.getActivityId(),
                    daoMuPvpData.getRound(),
                    userData.getUserId(),
                    goodsData.getGoodsId(),
                    JSON.toJSONString(userData.getProperty().getBuffDataList())
            ));
        }
    }

    /**
     * 广播用户属性
     *
     * @param sendAll
     */
    public void notifyUserProperty(DaoMuPvpUserData userData, boolean sendAll) {
        if (userData == null || userData.getRoomId() == 0) {
            return;
        }
        // 同步属性时处理下自动回血吧
        if (userData.getProperty().hpRecover()) {
            logger.info("【盗墓笔记】玩家:{},自动回血,当前血量:{}", userData.getUserId(), userData.getProperty().getCurHp());
        }

        DaoMuPvpProto.DaoMuPvpPropertySync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpPropertySync.newBuilder();
        syncMsg.setUserId(userData.getUserId());
        syncMsg.setProperty(DaoMuActivityPvpPb.parseDaoMuPvpPropertyTemp(userData.getProperty()));
        if (sendAll) {
            // 广播下属性变动
            sendPacketToAll(Protocol.U_DAOMU_PVP_PROPERTY_SYNC, syncMsg, getRoomUserListSync(userData.getRoomId()));
        } else {
            sendPacket(Protocol.U_DAOMU_PVP_PROPERTY_SYNC, syncMsg, userData.getUserId());
        }
    }

    /**
     * 打怪
     *
     * @param userId
     * @param bulletId
     * @param monsterId
     * @param targetCoordinate
     * @return
     */
    public int attackMonsterByCmd(long userId, String bulletId, String monsterId, Coordinate targetCoordinate) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        if (userData.getRoomId() == 0) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_DATA_NOT_FOUND;
        }
        DaoMuPvpRoomData roomData = getRoomData(userData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_DATA_NOT_FOUND;
        }
        // 校验子弹
        DaoMuPvpBulletData bulletData = null;
        for (DaoMuPvpBulletData data : userData.getBulletDataList()) {
            if (data.getBulletId().equals(bulletId)) {
                bulletData = data;
                break;
            }
        }
        if (bulletData == null) {
            return GameErrorCode.E_DAOMU_PVP_BULLET_NOT_FOUND;
        }
        DaoMuPvpMonsterData monsterData = roomData.getMonsterDataMap().get(monsterId);
        if (monsterData == null || !monsterData.isLive()) {
            return GameErrorCode.E_DAOMU_PVP_MONSTER_NOT_FOUND;
        }
        // 更新下怪物的坐标
        monsterMoveHandler(monsterData);
        long nowTime = System.currentTimeMillis();
        if (bulletData.getAttackTimeMap().containsKey(monsterId) && nowTime - bulletData.getAttackTimeMap().get(monsterId) < DateHelper.SECOND_MILLIONS) {
            return GameErrorCode.E_DAOMU_PVP_BULLET_ATTACK_LIMIT;
        }
        // 对怪物造成伤害
        monsterData.setAttackCnt(monsterData.getAttackCnt() - 1);
        if (!monsterData.isLive()) {
            // 死了,消失同步
            roomData.removeMonsterData(monsterId);

            // 死亡同步
            DaoMuPvpProto.DaoMuPvpMonsterDeleteSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpMonsterDeleteSync.newBuilder();
            syncMsg.setRoomId(roomData.getRoomId());
            syncMsg.setMonsterId(monsterId);
            sendPacketToAll(Protocol.U_DAOMU_PVP_MONSTER_DELETE_SYNC, syncMsg, getRoomUserListSync(roomData.getRoomId()));

            // 掉落物资
            List<DaoMuPvpGoodsData> goodsDataList = new ArrayList<>();
            DaoMuNpcConfig npcConfig = config.getDaoMuNpcConfigMap().get(monsterData.getFaceId());
            if (npcConfig != null) {
                Property property = PropertyHelper.parseNewStringToProperty(npcConfig.getSpecialRewards());
                if (!property.isNothing()) {
                    for (Map.Entry<Integer, BigInteger> entry : property.getGoods().entrySet()) {
                        for (int i = 0; i < entry.getValue().intValue(); i++) {
                            DaoMuPvpGoodsData goodsData = new DaoMuPvpGoodsData();
                            goodsData.setUid(randomUUID());
                            goodsData.setGoodsId(entry.getKey());
                            Coordinate coordinate = generateRandomCoordinate(monsterData.getCoordinate().getX(), monsterData.getCoordinate().getY(), config.getPvpCommonConfig().getMonsterDropRadius());
                            // 处理下边界
                            coordinate.setX(calcGoodsX(coordinate.getX()));
                            coordinate.setY(calcGoodsY(coordinate.getY()));
                            goodsData.setCoordinate(coordinate);
                            goodsData.setExpiredTime(System.currentTimeMillis() + config.getPvpCommonConfig().getGoodsDisappearTime() * DateHelper.SECOND_MILLIONS);
                            goodsDataList.add(goodsData);
                        }
                    }
                }
            }
            // 添加物资
            addRoomGoodsItem(userData.getRoomId(), goodsDataList);
        } else {
            // 把攻击者作为锁定目标
            monsterData.setLockUserId(userId);

            // 没死,位置同步
            monsterData.getPosition().setX(calcX(targetCoordinate.getX()));
            monsterData.getPosition().setY(calcY(targetCoordinate.getY()));
            monsterData.getPosition().setTime(nowTime);
            // 怪物位置广播
            addMonsterPosition(monsterData.getRoomId(), monsterData.getMonsterId(), monsterData.getPosition());
        }
        return 0;
    }


    /**
     * 校验升级
     *
     * @param userData
     * @return
     */
    private void addExp(DaoMuPvpUserData userData, long offsetExp) {
        if (userData == null) {
            return;
        }
        // 增加经验
        userData.getProperty().setExp(userData.getProperty().getExp() + offsetExp);

        userData.getProperty().setExpUpdateTime(System.currentTimeMillis());

        // 检测升级
        checkUpgradeLevel(userData);

        // 更新游戏服成就
        notifyAddExp(userData.getUserId(), offsetExp);
    }

    /**
     * 检测升级
     */
    private void checkUpgradeLevel(DaoMuPvpUserData userData) {
        if (userData == null) {
            return;
        }

        while (true) {
            DaoMuPlayerLevelConfig levelConfig = config.getDaoMuLevelConfigMap().getOrDefault(userData.getProperty().getLevel(), null);

            if (levelConfig == null || levelConfig.getPvpCost() == 0) {
                // 找不到配置或者满级，跳出循环
                break;
            }

            if (userData.getProperty().getExp() < levelConfig.getPvpCost()) {
                // 经验不够，跳出循环
                break;
            }

            // 升级
            userData.getProperty().setExp(userData.getProperty().getExp() - levelConfig.getPvpCost());
            userData.getProperty().setLevel(userData.getProperty().getLevel() + 1);
            // 记录下更新时间
            userData.getProperty().setExpUpdateTime(System.currentTimeMillis());

            // 学习技能次数+1
            userData.getProperty().setLearnSkillCnt(userData.getProperty().getLearnSkillCnt() + 1);
        }

        userData.setUpdateOption();
    }


    /**
     * 同步物资消失
     */
    public void notifyRemoveRoomItem(int roomId, int type, String id) {
        synchronized (needSyncRemoveRoomItemMap) {
            Map<String, Integer> roomMap = needSyncRemoveRoomItemMap.getOrDefault(roomId, new ConcurrentHashMap<>());
            roomMap.put(id, type);
            needSyncRemoveRoomItemMap.put(roomId, roomMap);
        }
    }

    public void notifyRemoveRoomItemTask() {
        for (Integer roomId : roomDataMap.keySet()) {
            Map<String, Integer> roomItemMap = new ConcurrentHashMap<>();
            synchronized (needSyncRemoveRoomItemMap) {
                roomItemMap = needSyncRemoveRoomItemMap.getOrDefault(roomId, new ConcurrentHashMap<>());
                needSyncRemoveRoomItemMap.put(roomId, new ConcurrentHashMap<>());
            }
            if (roomItemMap.size() == 0) {
                continue;
            }
            DaoMuPvpProto.DaoMuPvpRemoveRoomItemDataSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpRemoveRoomItemDataSync.newBuilder();
            syncMsg.setRoomId(roomId);
            for (Map.Entry<String, Integer> entry : roomItemMap.entrySet()) {
                DaoMuPvpProto.DaoMuPvpRoomItemSimpleTemp.Builder temp = DaoMuPvpProto.DaoMuPvpRoomItemSimpleTemp.newBuilder();
                temp.setId(entry.getKey());
                temp.setType(entry.getValue());
                syncMsg.addItem(temp);
            }
            sendPacketToAll(Protocol.U_DAOMU_PVP_REMOVE_ROOM_ITEM_SYNC, syncMsg, getRoomUserListSync(roomId));
        }
    }

    public void notifyAddRoomBoxItem(int roomId, List<DaoMuPvpBoxData> boxDataList) {
        if (boxDataList.size() == 0) {
            return;
        }
        DaoMuPvpProto.DaoMuPvpAddRoomItemDataSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpAddRoomItemDataSync.newBuilder();
        syncMsg.setRoomId(roomId);
        for (DaoMuPvpBoxData boxData : boxDataList) {
            syncMsg.addItem(DaoMuActivityPvpPb.parseDaoMuPvpRoomItemDataTemp(boxData));
        }
        sendPacketToAll(Protocol.U_DAOMU_PVP_ADD_ROOM_ITEM_SYNC, syncMsg, getRoomUserListSync(roomId));
    }

    public void notifyAddRoomGoodsItem(int roomId, List<DaoMuPvpGoodsData> goodsDataList) {
        DaoMuPvpProto.DaoMuPvpAddRoomItemDataSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpAddRoomItemDataSync.newBuilder();
        syncMsg.setRoomId(roomId);
        for (DaoMuPvpGoodsData goodsData : goodsDataList) {
            syncMsg.addItem(DaoMuActivityPvpPb.parseDaoMuPvpRoomItemDataTemp(goodsData));
        }
        sendPacketToAll(Protocol.U_DAOMU_PVP_ADD_ROOM_ITEM_SYNC, syncMsg, getRoomUserListSync(roomId));
    }

    /**
     * 攻击
     */
    public void attack(int roomId, long attackUserId, long attackedUserId) {
        long nowTime = System.currentTimeMillis();
        long hurtHp;
        DaoMuPvpUserData attackUserData;
        DaoMuPvpUserData attackedUserData;

        // 被攻击者等级
        int attackedLevel = 0;
        synchronized (getRoomLock(roomId)) {
            // 攻击者
            attackUserData = getUserData(attackUserId);
            // 被攻击者
            attackedUserData = getUserData(attackedUserId);
            if (attackUserData == null || attackedUserData == null || !attackUserData.isLive() || !attackedUserData.isLive()) {
                return;
            }
            if (attackedUserData.isProtectTime()) {
                // 无敌时间
                return;
            }
            // 计算伤害
            hurtHp = Math.max(1, attackUserData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_ATT.getType(), 0L) - attackedUserData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_DEFEND.getType(), 0L));
            attackedUserData.getProperty().subHp(hurtHp);
            logger.info("【盗墓笔记】玩家:{},受到玩家伤害:{},当前属性,当前血量:{}", attackedUserData.getUserId(), hurtHp, attackedUserData.getProperty().getCurHp());

            attackedLevel = attackedUserData.getProperty().getLevel();
        }

        // 攻击广播（同步所有人）
        DaoMuPvpProto.DaoMuPvpAttackedSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpAttackedSync.newBuilder();
        DaoMuPvpProto.DaoMuPvpAttackedTemp.Builder attackedTemp = DaoMuPvpProto.DaoMuPvpAttackedTemp.newBuilder();
        attackedTemp.setAttackUserId(attackUserId);
        attackedTemp.setAttackedUserId(attackedUserId);
        attackedTemp.setHp(hurtHp);
        attackedTemp.setAttackHp(attackUserData.getProperty().getCurHp());
        attackedTemp.setAttackedHp(attackedUserData.getProperty().getCurHp());
        attackedTemp.setAttackTime(nowTime);
        syncMsg.setAttack(attackedTemp);
        sendPacketToAll(Protocol.U_DAOMU_PVP_ATTACKED_SYNC, syncMsg, getRoomUserListSync(roomId));

        // 攻击后处理
        if (afterAttack(attackedUserData)) {

            // 记录击杀者信息
            attackedUserData.getProperty().getBattleRecord().setKillUserId(attackUserId);

            // 击杀人数++
            attackUserData.getProperty().getBattleRecord().setKillNum(attackUserData.getProperty().getBattleRecord().getKillNum() + 1);
            attackUserData.getProperty().getBattleRecord().setTotalKillNum(attackUserData.getProperty().getBattleRecord().getTotalKillNum() + 1);

            if (daoMuPvpData.isDisuse()) {
                // 淘汰处理
                handleOut(attackedUserData, 1, nowTime);

                // 同步下
                DaoMuPvpProto.DaoMuPvpOutSyncMsg.Builder outSyncMsg = DaoMuPvpProto.DaoMuPvpOutSyncMsg.newBuilder();
                outSyncMsg.setType(attackedUserData.getOutType());
                // 击杀者
                UserBaseInfo userBaseInfo = getUserBaseInfo(attackUserId);
                if (userBaseInfo != null) {
                    outSyncMsg.setUserInfo(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
                }
                outSyncMsg.setSettle(parseDaoMuPvpSettleTemp(attackedUserData));
                sendPacket(Protocol.U_DAOMU_PVP_OUT_SYNC, outSyncMsg, attackedUserData.getUserId());
            } else {
                // 通知被打人
                DaoMuPvpProto.DaoMuPvpAttackedUserSync.Builder syncUserMsg = DaoMuPvpProto.DaoMuPvpAttackedUserSync.newBuilder();
                syncUserMsg.setAttack(attackedTemp);
                // 击杀者信息
                DaoMuPvpProto.DaoMuPvpOtherPlayerInfo.Builder otherInfo = DaoMuActivityPvpPb.parseDaoMuPvpOtherPlayerInfo(attackUserData);
                if (otherInfo != null) {
                    syncUserMsg.setOtherInfo(otherInfo);
                }
                // 我的信息
                syncUserMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(attackedUserData));
                sendPacket(Protocol.U_DAOMU_PVP_ATTACKED_USER_SYNC, syncUserMsg, attackedUserId);
            }

            // 判断下是否推送击杀公告
            if (attackedLevel >= config.getPvpCommonConfig().getNoticeSingleKill()) {
                addNotice(attackUserId, attackedUserId, DaoMuPvpNoticeType.SINGLE_KILL.getType(), String.valueOf(attackedLevel));
            }
            if (config.getPvpCommonConfig().getNoticeOneLifeKillNum().contains(attackUserData.getProperty().getBattleRecord().getKillNum())) {
                addNotice(attackUserId, 0, DaoMuPvpNoticeType.ONE_LIFE_KILL.getType(), String.valueOf(attackUserData.getProperty().getBattleRecord().getKillNum()));
            }
            if (config.getPvpCommonConfig().getNoticeOneGameKillNum().contains(attackUserData.getProperty().getBattleRecord().getTotalKillNum())) {
                addNotice(attackUserId, 0, DaoMuPvpNoticeType.ONE_GAME_KILL.getType(), String.valueOf(attackUserData.getProperty().getBattleRecord().getTotalKillNum()));
            }

            // 记录下死亡日志
            if (!npcSet.contains(attackedUserId)) {
                CrossAutoLogMgr.add(new LogDaoMuPvpKill(
                        config.getActivityId(),
                        daoMuPvpData.getRound(),
                        attackedUserId,
                        attackedLevel,
                        1,
                        attackUserId,
                        0,
                        attackUserData.getProperty().getLevel(),
                        attackedLevel - attackedUserData.getProperty().getLevel(),
                        nowTime
                ));
            }

        }
    }

    /**
     * 是否死亡
     */
    public boolean afterAttack(DaoMuPvpUserData userData) {
        // 死亡处理
        if (userData.getProperty().getCurHp() > 0) {
            return false;
        }
        long nowTime = System.currentTimeMillis();
        // 标记死亡状态
        // 计算复活cd
        int reviveCd = Math.min(config.getPvpCommonConfig().getBaseReviveCd() + userData.getProperty().getReviveCnt() * config.getPvpCommonConfig().getAddReviveCd(), config.getPvpCommonConfig().getMaxReviveCd());
        userData.getProperty().setReviveTime(nowTime + DateHelper.SECOND_MILLIONS * reviveCd);

        Map<String, Long> needDestroyBulletMap = new HashMap<>();
        for (DaoMuPvpBulletData bulletData : userData.getBulletDataList()) {
            needDestroyBulletMap.put(bulletData.getBulletId(), bulletData.getUserId());
        }
        notifyDestroyBullet(userData.getRoomId(), needDestroyBulletMap);

        // 清空子弹
        userData.setBulletDataList(new ArrayList<>());

        // 处理下位置
        userData.getPosition().setDirX(0);
        userData.getPosition().setDirY(0);
        userData.getPosition().setTime(nowTime);

        // 计算掉落经验
        int dropExp = dropExp(userData);

        // 掉落武器的数量
        int dropBullet = dropBullet(userData);

        // 掉级处理
        dropLevelHandler(userData);

        // 更新下
        userData.setUpdateOption();

        // 掉个物资
        List<DaoMuPvpGoodsData> goodsDataList = new ArrayList<>();
        // 刷物资
        if (dropExp > 0) {
            DaoMuPvpGoodsData goodsData = new DaoMuPvpGoodsData();
            goodsData.setUid(randomUUID());
            // 写死道具id
            goodsData.setGoodsId(PVP_DROP_EXP_ID);
            goodsData.setExtraData(String.valueOf(dropExp));
            Coordinate coordinate = generateRandomCoordinate(userData.getCoordinate().getX(), userData.getCoordinate().getY(), config.getPvpCommonConfig().getRoleDropRadius());
            // 处理下边界
            coordinate.setX(calcGoodsX(coordinate.getX()));
            coordinate.setY(calcGoodsY(coordinate.getY()));
            goodsData.setCoordinate(coordinate);
            goodsData.setExpiredTime(nowTime + config.getPvpCommonConfig().getGoodsDisappearTime() * DateHelper.SECOND_MILLIONS);
            goodsDataList.add(goodsData);
        }
        if (dropBullet > 0) {
            DaoMuPvpGoodsData goodsData = new DaoMuPvpGoodsData();
            goodsData.setUid(randomUUID());
            // 写死道具id
            goodsData.setGoodsId(PVP_DROP_BULLET_ID);
            goodsData.setExtraData(String.valueOf(dropBullet));
            Coordinate coordinate = generateRandomCoordinate(userData.getCoordinate().getX(), userData.getCoordinate().getY(), config.getPvpCommonConfig().getRoleDropRadius());
            // 处理下边界
            coordinate.setX(calcGoodsX(coordinate.getX()));
            coordinate.setY(calcGoodsY(coordinate.getY()));
            goodsData.setCoordinate(coordinate);
            goodsData.setExpiredTime(nowTime + config.getPvpCommonConfig().getGoodsDisappearTime() * DateHelper.SECOND_MILLIONS);
            goodsDataList.add(goodsData);
        }
        // 掉个物资
        if (!StringUtils.isNullOrEmpty(config.getPvpCommonConfig().getPvpDropsHpItem())) {
            Property property = PropertyHelper.parseNewStringToProperty(config.getPvpCommonConfig().getPvpDropsHpItem());
            if (!property.isNothing()) {
                for (Map.Entry<Integer, BigInteger> entry : property.getGoods().entrySet()) {
                    for (int i = 0; i < entry.getValue().intValue(); i++) {
                        DaoMuPvpGoodsData goodsData = new DaoMuPvpGoodsData();
                        goodsData.setUid(randomUUID());
                        goodsData.setGoodsId(entry.getKey());
                        Coordinate coordinate = generateRandomCoordinate(userData.getCoordinate().getX(), userData.getCoordinate().getY(), config.getPvpCommonConfig().getRoleDropRadius());
                        // 处理下边界
                        coordinate.setX(calcGoodsX(coordinate.getX()));
                        coordinate.setY(calcGoodsY(coordinate.getY()));
                        goodsData.setCoordinate(coordinate);
                        goodsData.setExpiredTime(System.currentTimeMillis() + config.getPvpCommonConfig().getGoodsDisappearTime() * DateHelper.SECOND_MILLIONS);
                        goodsDataList.add(goodsData);
                    }
                }
            }
        }
        addRoomGoodsItem(userData.getRoomId(), goodsDataList);
        return true;
    }

    /**
     * 掉落经验
     */
    private int dropExp(DaoMuPvpUserData userData) {
        if (userData == null) {
            return 0;
        }
        DaoMuPlayerLevelConfig levelConfig = config.getDaoMuLevelConfigMap().get(userData.getProperty().getLevel());
        if (levelConfig == null) {
            return 0;
        }
        String endDropsExp = levelConfig.getEndDropsExp();
        if (endDropsExp == null || "0".equals(endDropsExp)) {
            return 0;
        }
        String[] split = endDropsExp.split(";");
        return randomHelper.next(Integer.parseInt(split[0]), Integer.parseInt(split[1]) + 1);
    }

    /**
     * 掉落武器
     */
    private int dropBullet(DaoMuPvpUserData userData) {
        if (userData == null) {
            return 0;
        }
        DaoMuPlayerLevelConfig levelConfig = config.getDaoMuLevelConfigMap().get(userData.getProperty().getLevel());
        if (levelConfig == null) {
            return 0;
        }
        String endDropsArmsNum = levelConfig.getEndDropsArmsNum();
        if (endDropsArmsNum == null || "0".equals(endDropsArmsNum)) {
            return 0;
        }
        String[] split = endDropsArmsNum.split(";");
        return randomHelper.next(Integer.parseInt(split[0]), Integer.parseInt(split[1]) + 1);
    }

    /**
     * 掉级处理
     */
    private void dropLevelHandler(DaoMuPvpUserData userData) {
        if (userData == null) {
            return;
        }
        // 如果是淘汰模式，不处理
        if (daoMuPvpData.isDisuse()) {
            return;
        }
        DaoMuPlayerLevelConfig levelConfig = config.getDaoMuLevelConfigMap().get(userData.getProperty().getLevel());
        if (levelConfig == null) {
            return;
        }
        String endDowngrade = levelConfig.getEndDowngrade();
        if (endDowngrade == null || "0".equals(endDowngrade)) {
            return;
        }
        String[] split = endDowngrade.split(";");
        userData.getProperty().handleDropLevel(randomHelper.next(Integer.parseInt(split[0]), Integer.parseInt(split[1]) + 1));

        // 重新计算属性
        changeAttributeMap(userData);
    }

    public void addRoomGoodsItem(int roomId, List<DaoMuPvpGoodsData> goodsDataList) {
        if (goodsDataList == null || goodsDataList.size() == 0) {
            return;
        }
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData == null) {
            return;
        }
        for (DaoMuPvpGoodsData goodsData : goodsDataList) {
            roomData.addGoodsData(goodsData);
        }
        roomData.setUpdateOption();
        // 广播
        notifyAddRoomGoodsItem(roomId, goodsDataList);
    }

    /**
     * 移除宝箱
     *
     * @param roomId
     * @param boxId
     */
    public void removeBoxItem(int roomId, String boxId) {
        synchronized (getRoomLock(roomId)) {
            DaoMuPvpRoomData roomData = getRoomData(roomId);
            if (roomData == null) {
                return;
            }
            // 下次刷新时间
            roomData.getBoxDataList().stream()
                    .filter(boxData -> Objects.equals(boxData.getUid(), boxId))
                    .findFirst()
                    .ifPresent(boxData -> boxData.setNextRefreshTime(System.currentTimeMillis() + config.getPvpConfigMap().get(roomData.getFloor()).getBoxRefreshTime() * DateHelper.SECOND_MILLIONS));
        }
        // 同步销毁
        notifyRemoveRoomItem(roomId, 1, boxId);
    }


    /**
     * 移除物资
     *
     * @param roomId
     */
    public void removeRoomGoods(int roomId, String uid) {
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData == null) {
            return;
        }
        roomData.removeGoodsData(uid);
        // 同步销毁
        notifyRemoveRoomItem(roomId, 2, uid);
    }


    /**
     * 刷物资定时任务
     */
    public void refreshRoomGoodsTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        for (Integer roomId : roomDataMap.keySet()) {
            synchronized (getRoomLock(roomId)) {
                DaoMuPvpRoomData roomData = getRoomData(roomId);
                // 判断房间状态
                if (roomData.isCollapse()) {
                    continue;
                }
                DaoMuPvpConfig pvpConfig = config.getPvpConfigMap().get(roomData.getFloor());
                if (pvpConfig == null) {
                    continue;
                }
                // 处理下宝箱
                List<DaoMuPvpBoxData> boxDataList = new ArrayList<>();
                for (DaoMuPvpBoxData boxData : roomData.getBoxDataList()) {
                    if (nowTime >= boxData.getNextRefreshTime() && !boxData.isLive()) {
                        // 要给下新的id
                        boxData.setUid(randomUUID());
                        boxData.setAttackCnt(pvpConfig.getBoxHits());

                        // 宝箱奖励
                        List<Integer> goodsList = new ArrayList<>();
                        Property property = PropertyHelper.parseNewStringToProperty(pvpConfig.getBoxWeights());
                        if (!property.isNothing()) {
                            for (Map.Entry<Integer, BigInteger> entry : property.getGoods().entrySet()) {
                                for (int i = 0; i < entry.getValue().intValue(); i++) {
                                    goodsList.add(entry.getKey());
                                }
                            }
                        }
                        boxData.setGoodsList(goodsList);
                        boxDataList.add(boxData);
                    }
                }
                // 广播
                notifyAddRoomBoxItem(roomData.getRoomId(), boxDataList);
            }
        }
    }

    /**
     * 回收物资定时任务
     */
    public void recycleRoomGoodsTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            // 判断房间状态
            if (roomData.isCollapse()) {
                continue;
            }
            // 处理物资
            List<DaoMuPvpGoodsData> goodsDataList = new ArrayList<>(roomData.getGoodsDataMap().values());
            for (DaoMuPvpGoodsData goodsData : goodsDataList) {
                if (goodsData.getExpiredTime() != 0 && nowTime > goodsData.getExpiredTime()) {
                    // 同步销毁
                    removeRoomGoods(roomData.getRoomId(), goodsData.getUid());
                }
            }

        }
    }

    /**
     * 添加子弹
     */
    public void createBullet(DaoMuPvpUserData userData, int count) {
        if (userData == null) {
            return;
        }
        // 可生成的武器数
        int genCount = (int) Math.min(count, userData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_BULLET_LIMIT.getType(), 0L) - userData.getBulletDataList().size());
        if (genCount <= 0) {
            // 子弹大于上限
            return;
        }
        for (int i = 0; i < genCount; i++) {
            // 加到用户身上
            userData.addBulletData(new DaoMuPvpBulletData(randomUUID(), userData.getUserId()));
        }

        // 刷新下子弹
        refreshBulletList(userData);


        // 检测碰撞
        if (!userData.isProtectTime()) {
            // 检验玩家碰撞
            checkPlayerCollision(userData);
        }

        // 同步下
        DaoMuPvpProto.DaoMuPvpCreateBulletSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpCreateBulletSync.newBuilder();
        for (DaoMuPvpBulletData data : userData.getBulletDataList()) {
            syncMsg.addBullet(DaoMuActivityPvpPb.parseDaoMuPvpBulletTemp(data));
        }
        sendPacketToAll(Protocol.U_DAOMU_PVP_CREATE_BULLET, syncMsg, getRoomUserListSync(userData.getRoomId()));
    }


    /**
     * 处理复活逻辑
     */
    public void handleRevive(DaoMuPvpUserData userData) {
        if (userData == null || userData.isOut()) {
            return;
        }
        DaoMuPvpProperty property = userData.getProperty();

        // 做个数据修复
        if (userData.getProperty().getLevel() == 0) {
            userData.getProperty().setLevel(1);
        }

        if (property.getCurHp() > 0) {
            // 无需复活
            return;
        }
        long nowTime = System.currentTimeMillis();
        if (nowTime < property.getReviveTime()) {
            // 未到复活时间
            return;
        }

        // 先检测下升级
        checkUpgradeLevel(userData);

        DaoMuPlayerLevelConfig levelConfig = config.getDaoMuLevelConfigMap().get(userData.getProperty().getLevel());
        if (levelConfig == null) {
            return;
        }

        // 回出生点
        // 处理复活
        DaoMuPvpBirthData birthData = randomBirth(userData.getRoomId());
        if (birthData == null) {
            // 找不到出生点
            return;
        }
        // 更新下位置
        userData.getPosition().setX(calcX( birthData.getCoordinate().getX()));
        userData.getPosition().setY(calcY(birthData.getCoordinate().getY()));
        userData.getPosition().setDirX(0);
        userData.getPosition().setDirY(0);
        userData.getPosition().setTime(nowTime);



        // 开始复活
        property.setCurHp(property.getMaxHp());

        // 创建初始武器
        createBullet(userData, levelConfig.getPvpInitialArmsNum());

        // 加个无敌时间
        property.setProtectTime(nowTime + config.getPvpCommonConfig().getProtectCd() * DateHelper.SECOND_MILLIONS);
        // 加下复活次数
        property.setReviveCnt(property.getReviveCnt() + 1);

        property.getBattleRecord().setDropSkillList(new ArrayList<>());
        property.getBattleRecord().setDropLevel(0);
        property.getBattleRecord().setKillUserId(0);
        property.getBattleRecord().setKillMonsterId(0);
        property.getBattleRecord().setKillNum(0);

        userData.setUpdateOption();

        // 广播位置
        addPlayerPosition(userData.getRoomId(), userData.getUserId(), userData.getPosition());
    }


    /**
     * 进入pvp活动
     */
    public int enterActivityByCmd(long userId) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        // 判断资格
        if (!userData.isAble()) {
            return GameErrorCode.E_DAOMU_PVP_NO_ABLE;
        }
        if (userData.isOut()) {
            return GameErrorCode.E_DAOMU_PVP_IS_OUT;
        }
        // 如果不存在房间，初始化分配一个
        if (userData.getRoomId() == 0) {
            allotUser(userId);
        }

        // 处理下复活
        handleRevive(userData);

        // 更新下属性吧
        changeAttributeMap(userData);

        DaoMuPvpProto.DaoMuPvpEnterActivityRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpEnterActivityRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
        DaoMuPvpProto.DaoMuPvpRoomDataTemp.Builder roomDataTemp = parseDaoMuPvpRoomDataTemp(userData.getRoomId());
        if (roomDataTemp != null) {
            respMsg.setRoomData(roomDataTemp);
        }
        // 拉下公告
        List<DaoMuPvpNoticeData> noticeList = new ArrayList<>(noticeDataList);
        for (DaoMuPvpNoticeData noticeData : noticeList) {
            respMsg.addNotice(DaoMuActivityPvpPb.parseDaoMuPvpNoticeTemp(noticeData));
        }
        sendPacket(Protocol.U_DAOMU_PVP_ENTER_ACTIVITY, respMsg, userId);
        return 0;
    }

    public DaoMuPvpProto.DaoMuPvpRoomDataTemp.Builder parseDaoMuPvpRoomDataTemp(int roomId) {
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData == null) {
            return null;
        }
        DaoMuPvpProto.DaoMuPvpRoomDataTemp.Builder builder = DaoMuPvpProto.DaoMuPvpRoomDataTemp.newBuilder();
        builder.setRoomId(roomId);
        // 房间内用户信息
        ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomId);
        for (Long otherUserId : roomUserList) {
            DaoMuPvpUserData otherUserData = getUserData(otherUserId);
            if (otherUserData == null) {
                continue;
            }
            DaoMuPvpProto.DaoMuPvpOtherPlayerInfo.Builder otherInfo = DaoMuActivityPvpPb.parseDaoMuPvpOtherPlayerInfo(otherUserData);
            if (otherInfo != null) {
                builder.addOtherInfo(otherInfo);
            }
        }
        // 房间物资信息
        // 宝箱
        for (DaoMuPvpBoxData boxData : roomData.getBoxDataList()) {
            if (boxData == null || !boxData.isLive()) {
                continue;
            }
            builder.addItem(DaoMuActivityPvpPb.parseDaoMuPvpRoomItemDataTemp(boxData));
        }
        // 物资
        List<DaoMuPvpGoodsData> goodsDataList = new ArrayList<>(roomData.getGoodsDataMap().values());
        for (DaoMuPvpGoodsData rewardData : goodsDataList) {
            builder.addItem(DaoMuActivityPvpPb.parseDaoMuPvpRoomItemDataTemp(rewardData));
        }
        // 出生点
        for (DaoMuPvpBirthData birthData : roomData.getBirthDataList()) {
            builder.addBirthCoordinate(DaoMuActivityPvpPb.parseDaoMuPvpCoordinateTemp(birthData.getCoordinate()));
        }
        // 怪物信息
        for (DaoMuPvpMonsterData monsterData : roomData.getMonsterDataMap().values()) {
            builder.addMonster(DaoMuActivityPvpPb.parseDaoMuPvpMonsterTemp(monsterData));
        }
        // 收圈时间
        builder.setShrinkStartTime(roomData.getShrinkStartTime());
        return builder;
    }


    /**
     * 加入房间
     */
    public int joinRoomByCmd(long userId, int roomId) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData == null) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_DATA_NOT_FOUND;
        }
        if (roomData.isCollapse()) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_IS_COLLAPSE;
        }
        if (getRoomUserList(roomId).size() >= config.getPvpConfigMap().get(roomData.getFloor()).getPeople()) {
            // 人数达到上限
            return GameErrorCode.E_DAOMU_PVP_ROOM_PEOPLE_LIMIT;
        }
        // 离开旧房间
        if (userData.getRoomId() != 0) {
            leaveRoom(userData, 1);
        }
        // 加入新房间
        joinRoom(roomId, userData, null);
        DaoMuPvpProto.DaoMuPvpJoinRoomRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpJoinRoomRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
        DaoMuPvpProto.DaoMuPvpRoomDataTemp.Builder roomDataTemp = parseDaoMuPvpRoomDataTemp(userData.getRoomId());
        if (roomDataTemp != null) {
            respMsg.setRoomData(roomDataTemp);
        }
        sendPacket(Protocol.U_DAOMU_PVP_JOIN_ROOM, respMsg, userId);
        return 0;
    }

    /**
     * 累计进入多少人
     *
     * @param floor
     */
    public void addFloorCnt(int floor) {
        daoMuPvpData.getFloorCntMap().put(floor, daoMuPvpData.getFloorCntMap().getOrDefault(floor, 0) + 1);
    }

    /**
     * 获取某层累计进入的人数
     */
    public int getFloorCnt(int floor) {
        return daoMuPvpData.getFloorCntMap().getOrDefault(floor, 0);
    }

    /**
     * 获取某层的人数（实时）
     */
    private int getFloorUserCnt(int floor) {
        int cnt = 0;
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.getFloor() != floor) {
                continue;
            }
            ConcurrentHashSet<Long> list = getRoomUserList(roomData.getRoomId());
            cnt += list.size();
        }
        return cnt;
    }

    /**
     * 进入下一层
     */
    public int enterNextFloorByCmd(long userId) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpRoomData roomData = getRoomData(userData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_DATA_NOT_FOUND;
        }
        int curFloor = roomData.getFloor();
        DaoMuPvpConfig curFloorConfig = config.getPvpConfigMap().get(curFloor);
        if (curFloorConfig == null) {
            return GameErrorCode.E_DAOMU_PVP_CONFIG_NOT_FOUND;
        }
        int nextFloor = curFloor + 1;
        DaoMuPvpConfig nextFloorConfig = config.getPvpConfigMap().get(nextFloor);
        if (nextFloorConfig == null) {
            return GameErrorCode.E_DAOMU_PVP_CONFIG_NOT_FOUND;
        }
        // 判断下时间
        if (System.currentTimeMillis() < getBattleStartTime() + curFloorConfig.getUpgradeNeedTime() * DateHelper.SECOND_MILLIONS) {
            return GameErrorCode.E_DAOMU_PVP_TIME_NOT_ENOUGH;
        }
        // 是否有资格进入下一层
        if (userData.getProperty().getLevel() < curFloorConfig.getUpgradeNeedLevel()) {
            return GameErrorCode.E_DAOMU_PVP_LEVEL_NOT_ENOUGH;
        }
        // 判断下一层的人数上限
        if (isFloorLimit(nextFloor)) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_PEOPLE_LIMIT;
        }
        // 获取下一层的随机房间
        DaoMuPvpBirthData initBirthData = randomBirthInFloor(nextFloor);
        if (initBirthData == null) {
            // 找不到出生点说明人满了
            return GameErrorCode.E_DAOMU_PVP_ROOM_PEOPLE_LIMIT;
        }
        // 离开原来的房间
        if (userData.getRoomId() != 0) {
            leaveRoom(userData, 1);
        }
        // 进入新房间
        joinRoom(initBirthData.getRoomId(), userData, initBirthData);
        // 添加人数
        addFloorCnt(nextFloor);

        try {
            logger.info("enterNextFloorByCmd userId:{} curFloor:{} nextFloor:{} roomId:{}", userId, curFloor, nextFloor, userData.getRoomId());
        } catch (Exception e) {
            logger.error("enterNextFloorByCmd error", e);
        }

        // 检测是否提前坍塌
        int floorCnt = getFloorCnt(nextFloor);

        if (floorCnt >= nextFloorConfig.getCollapseCondPeople()) {
            // 本层坍塌
            handleCollapse(curFloor);
        }

        // 检测是否开启淘汰
        if (nextFloorConfig.getDisuse() == 1 && floorCnt == 1) {
            // 开启淘汰模式(全场开启)
            daoMuPvpData.setDisuse(true);
        }

        if (config.getPvpCommonConfig().getNoticeCollapseNum().containsKey(nextFloor) && config.getPvpCommonConfig().getNoticeCollapseNum().get(nextFloor).contains(floorCnt)) {
            addNotice(0, 0, DaoMuPvpNoticeType.COLLAPSE_BY_NUM.getType(), nextFloor + "#" + floorCnt);
        }

        // 检测提前坍塌公告
        if (config.getPvpCommonConfig().getNoticeDisuseNum().containsKey(nextFloor) && config.getPvpCommonConfig().getNoticeDisuseNum().get(nextFloor) == floorCnt) {
            // 坍塌公告
            addNotice(userId, 0, DaoMuPvpNoticeType.DISUSE_BY_NUM.getType(), String.valueOf(nextFloor));
        }

        DaoMuPvpProto.DaoMuPvpEnterNextFloorRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpEnterNextFloorRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setMyInfo(DaoMuActivityPvpPb.parseDaoMuPvpPlayerInfo(userData));
        DaoMuPvpProto.DaoMuPvpRoomDataTemp.Builder roomDataTemp = parseDaoMuPvpRoomDataTemp(userData.getRoomId());
        if (roomDataTemp != null) {
            respMsg.setRoomData(roomDataTemp);
        }
        sendPacket(Protocol.U_DAOMU_PVP_ENTER_NEXT_FLOOR, respMsg, userId);

        // 特殊判断下 清掉当前层的房间数据，节省内存
        if (getFloorUserCnt(1) == 0) {
            if (getFloorUserCnt(2) == 0) {
                // 清理1、2层房间数据
                for (DaoMuPvpRoomData data : roomDataMap.values()) {
                    if (data.getFloor() == 1 || data.getFloor() == 2) {
                        data.clearData();
                    }
                }
            } else {
                // 清理1层房间数据
                for (DaoMuPvpRoomData data : roomDataMap.values()) {
                    if (data.getFloor() == 1) {
                        data.clearData();
                    }
                }
            }
        }
        return 0;
    }

    /**
     * 该层人数限制
     *
     * @param nextFloor 下一层
     */
    private boolean isFloorLimit(int nextFloor) {
        DaoMuPvpConfig pvpConfig = config.getPvpConfigMap().get(nextFloor);
        if (pvpConfig == null) {
            // 没配置，不让去
            return true;
        }
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                continue;
            }
            if (roomData.getFloor() != nextFloor) {
                continue;
            }
            if (getRoomUserList(roomData.getRoomId()).size() < pvpConfig.getPeople()) {
                return false;
            }
        }

        return true;
    }

    /**
     * 坍塌处理
     */
    private void handleCollapse(int floor) {
        if (daoMuPvpData.isSettle()) {
            // 结算了就不处理坍塌了
            return;
        }
        if (daoMuPvpData.getCollapseFloors().contains(floor)) {
            // 已塌，不处理
            return;
        }
        if (floor == 3) {
            // 防错，第三层不支持坍塌，配置配了也不支持，设计如此
            return;
        }
        // 坍塌公告
        addNotice(0, 0, DaoMuPvpNoticeType.COLLAPSE.getType(), String.valueOf(floor));

        daoMuPvpData.getCollapseFloors().add(floor);
        // 坍塌时间
        long collapseTime = System.currentTimeMillis();

        try {
            logger.info("handleCollapse activityId:{},floor:{},collapseTime:{}", config.getActivityId(), floor, collapseTime);
        } catch (Exception e) {
            logger.error("handleCollapse error", e);
        }

        Set<Long> needSyncUser = new HashSet<>();
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.getFloor() != floor) {
                continue;
            }
            // 坍塌
            roomData.setCollapse(true);
            // 清空房间数据
            roomData.clearData();
            // 用户淘汰处理
            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomData.getRoomId());
            for (Long userId : roomUserList) {
                handleOut(getUserData(userId), 2, collapseTime);
                needSyncUser.add(userId);
            }
        }

        // 通知下坍塌淘汰
        for (Long userId : needSyncUser) {
            DaoMuPvpUserData userData = getUserData(userId);
            if (userData == null) {
                continue;
            }
//            // 获取下淘汰时的排名
//            int myRank = aliveRankListModel.getMyRank(userData.getUserId());
//            if (myRank == 1) {
//                // 第一名去结算那边同步
//                settle();
//            } else {
                // 其他人同步下淘汰信息
                DaoMuPvpProto.DaoMuPvpOutSyncMsg.Builder syncMsg = DaoMuPvpProto.DaoMuPvpOutSyncMsg.newBuilder();
                syncMsg.setType(userData.getOutType());
                syncMsg.setSettle(parseDaoMuPvpSettleTemp(userData));
                sendPacket(Protocol.U_DAOMU_PVP_OUT_SYNC, syncMsg, userId);
//            }
        }

        // 触发下一层的收圈
        DaoMuPvpConfig nextFloorConfig = config.getPvpConfigMap().get(floor + 1);
        // 是否会收圈
        if (nextFloorConfig != null && !"0".equals(nextFloorConfig.getShrinkCircle())) {
            for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
                if (roomData.getFloor() != floor + 1) {
                    continue;
                }
                // 开始收圈
                roomData.setShrinkStartTime(collapseTime);

                // 需要广播下
                notifyRoomData(roomData.getRoomId());
            }

            // 缩圈公告
            addNotice(0, 0, DaoMuPvpNoticeType.START_SHRINK_CIRCLE.getType(), String.valueOf(floor + 1));
        }
    }

    /**
     * 广播房间信息
     */
    private void notifyRoomData(int roomId) {
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData == null) {
            return;
        }
        DaoMuPvpProto.DaoMuPvpRoomDataSyncMsg.Builder syncMsg = DaoMuPvpProto.DaoMuPvpRoomDataSyncMsg.newBuilder();
        syncMsg.setRoomId(roomId);
        syncMsg.setShrinkStartTime(roomData.getShrinkStartTime());
        sendPacketToAll(Protocol.U_DAOMU_PVP_ROOM_DATA_SYNC, syncMsg, getRoomUserListSync(roomId));
    }

    private void handleOut(DaoMuPvpUserData userData, int outType, long outTime) {
        if (userData == null) {
            return;
        }
        userData.setOut(true);
        userData.setOutType(outType);
        // 所有人都减掉1秒，所以还是公平的
        userData.setOutTime(outTime - DateHelper.SECOND_MILLIONS);

        // 离开房间
        leaveRoom(userData, 2);

        // param 等级#经验#经验更新时间#锁定时藏品分
        List<String> paramList = new ArrayList<>();
        paramList.add(String.valueOf(userData.getProperty().getLevel()));
        paramList.add(String.valueOf(userData.getProperty().getExp()));
        paramList.add(String.valueOf(userData.getProperty().getExpUpdateTime()));
        paramList.add(String.valueOf(userData.getCollectionScore()));
        // 更新存活榜单
        aliveRankListModel.rankChange(new DaoMuRank(userData.getUserId(), userData.getOutTime(), StringUtils.listToString(paramList, "#"), userData.getOutTime()));

        // 存活计数
        aliveCnt.decrementAndGet();

        try {
            logger.info("handleOut activityId:{},userId:{},outType:{},outTime:{},aliveCnt:{}", config.getActivityId(), userData.getUserId(), outType, outTime, aliveCnt.get());
        } catch (Exception e) {
            logger.error("handleOut error", e);
        }

        if (!npcSet.contains(userData.getUserId()) && outType == 2) {
            CrossAutoLogMgr.add(new LogDaoMuPvpKill(
                    config.getActivityId(),
                    daoMuPvpData.getRound(),
                    userData.getUserId(),
                    userData.getProperty().getLevel(),
                    4,
                    0, 0, 0,
                    0,
                    userData.getOutTime()
            ));
        }
    }

    private DaoMuPvpProto.DaoMuPvpSettleTemp.Builder parseDaoMuPvpSettleTemp(DaoMuPvpUserData userData) {
        if (userData == null) {
            return null;
        }
        DaoMuPvpProto.DaoMuPvpSettleTemp.Builder builder = DaoMuPvpProto.DaoMuPvpSettleTemp.newBuilder();
        builder.setUserId(userData.getUserId());
        UserBaseInfo userBaseInfo = getUserBaseInfo(userData.getUserId());
        if (userBaseInfo != null) {
            builder.setUserInfo(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
        }
        // 淘汰排名
        builder.setRank(aliveRankListModel.getMyRank(userData.getUserId()));
        builder.setOutTime(userData.getOutTime());
        return builder;
    }

    /**
     * 用户报名
     *
     * @param userId
     * @param type
     * @param collectionScore
     * @param collectionLevel
     * @return
     */
    public int userApplyByCmd(long userId, int type, int collectionLevel, long collectionScore) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        long nowTime = System.currentTimeMillis();
        DaoMuPvpProto.DaoMuPvpUserApplyRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpUserApplyRespMsg.newBuilder();
        if (type == 0) {
            // 如果报名了，需要更新榜单  ，仅在报名期更新
            if (userData.isApply()) {
                if (isApplyTime()) {
                    if (collectionLevel != userData.getCollectionLevel() || collectionScore != userData.getCollectionScore()) {
                        userData.setCollectionLevel(collectionLevel);
                        userData.setCollectionScore(collectionScore);
                        userData.setPveUploadTime(nowTime);
                        // 更新榜单
                        applyRankListModel.rankChange(new DaoMuRank(userId, userData.getCollectionLevel(), String.valueOf(userData.getCollectionScore()), userData.getPveUploadTime()));

                        // 重新计算用户属性
                        calcUserBaseAttribute(userData);
                    }
                }
                // 获取榜单信息
                int myRank = -1;
                long myValue = 0;
                String myParam = "";
                int rankNum = 0;
                for (DaoMuRank rank : applyRankListModel.getRankList()) {
                    rankNum++;
                    if (rank.getUserId() == userId) {
                        myRank = rankNum;
                        myValue = rank.getValue();
                        myParam = rank.getParam();
                        break;
                    }
                }
                respMsg.setMyRank(parseDaoMuPvpRankTemp(userId, myValue, myParam, myRank));
            }
        } else if (type == 1) {
            // 报名
            if (userData.isApply()) {
                // 已报名，无需再报名
                return GameErrorCode.E_DAOMU_PVP_IS_APPLY;
            }
            // 判断下是否在报名时间
            if (!isApplyTime()) {
                return GameErrorCode.E_DAOMU_PVP_NOT_APPLY_TIME;
            }
            if (collectionLevel <= 0 || collectionScore <= 0) {
                return GameErrorCode.E_DAOMU_PVP_NO_ABLE;
            }
            userData.setCollectionLevel(collectionLevel);
            userData.setCollectionScore(collectionScore);
            userData.setPveUploadTime(nowTime);
            // 标记报名状态
            userData.setApply(true);
            // 更新榜单
            applyRankListModel.rankChange(new DaoMuRank(userId, userData.getCollectionLevel(), String.valueOf(userData.getCollectionScore()), userData.getPveUploadTime()));

            // 重新计算用户属性
            calcUserBaseAttribute(userData);
        }
        respMsg.setRet(0);
        respMsg.setApply(userData.isApply());
        respMsg.setAble(userData.isAble());
        respMsg.setOut(userData.isOut());
        respMsg.setSettle(daoMuPvpData.isSettle());
        // 回包
        sendPacket(Protocol.U_DAOMU_PVP_USER_APPLY, respMsg, userId);
        return 0;
    }

    /**
     * 获取榜单积分详情
     */
    public int getScoreRankDetail(long userId, long targetUserId) {
        DaoMuPvpUserData targetUserData = getUserData(targetUserId);
        if (targetUserData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpProto.DaoMuPvpScoreRankDetailRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpScoreRankDetailRespMsg.newBuilder();
        for (Map.Entry<Integer, Integer> entry : targetUserData.getRoundRank().entrySet()) {
            DaoMuPvpProto.DaoMuPvpScoreRankDetailTemp.Builder temp = DaoMuPvpProto.DaoMuPvpScoreRankDetailTemp.newBuilder();
            temp.setRound(entry.getKey());
            temp.setRank(entry.getValue());
            temp.setScore(Math.toIntExact(targetUserData.getRoundScore().getOrDefault(entry.getKey(), 0L)));
            respMsg.addDetail(temp);
        }
        respMsg.setRet(0);
        sendPacket(Protocol.U_DAOMU_PVP_GET_SCORE_RANK_DETAIL, respMsg, userId);
        return 0;
    }

    /**
     * 用户报名榜单信息
     */
    public int getUserApplyRankListByCmd(long userId) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpProto.DaoMuGetUserApplyRankListRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuGetUserApplyRankListRespMsg.newBuilder();
        int rankNum = 0;
        for (DaoMuRank rank : applyRankListModel.getRankList()) {
            rankNum++;
            respMsg.addRankList(parseDaoMuPvpRankTemp(rank.getUserId(), rank.getValue(), rank.getParam(), rankNum));
            if (rank.getUserId() == userId) {
                // 我的排名
                respMsg.setMyRank(parseDaoMuPvpRankTemp(rank.getUserId(), rank.getValue(), rank.getParam(), rankNum));
            }
        }
        respMsg.setRet(0);
        // 回包
        sendPacket(Protocol.U_DAOMU_PVP_GET_USER_APPLY_RANK_LIST, respMsg, userId);
        return 0;
    }

    /**
     * 用户等级榜单信息(存活榜单)
     */
    public int getUserLevelRankListByCmd(long userId) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpProto.DaoMuPvpGetLevelRankListRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpGetLevelRankListRespMsg.newBuilder();
        int rankNum = 0;
        for (DaoMuRank rank : aliveRankListModel.getRankList()) {
            rankNum++;
            if (rankNum <= 3) {
                // 前三才加入展示
                respMsg.addRankList(parseDaoMuPvpRankTemp(rank.getUserId(), rank.getValue(), rank.getParam(), rankNum));
            }
            if (rank.getUserId() == userId) {
                // 我的排名
                respMsg.setMyRank(parseDaoMuPvpRankTemp(rank.getUserId(), rank.getValue(), rank.getParam(), rankNum));
            }
        }
        respMsg.setRet(0);
        // 获取当前的人数
        respMsg.setLeftNum(aliveCnt.get());
        // 回包
        sendPacket(Protocol.U_DAOMU_PVP_GET_USER_LEVEL_RANK_LIST, respMsg, userId);
        return 0;
    }

    /**
     * 切换角色id
     */
    public int changeRole(long userId, int roleId) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        userData.setRoleId(roleId);
        DaoMuPvpProto.DaoMuPvpChangeRoleRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpChangeRoleRespMsg.newBuilder();
        respMsg.setRet(0);
        sendPacket(Protocol.U_DAOMU_PVP_CHANGE_ROLE, respMsg, userId);
        return 0;
    }

    /**
     * 获取用户基础属性
     */
    public int getBaseAttribute(long userId) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpProto.DaoMuPvpGetBaseAttributeRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpGetBaseAttributeRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setRoleId(userData.getRoleId());
        for (Map.Entry<Integer, Long> entry : userData.getProperty().getBaseAttributeMap().entrySet()) {
            DaoMuProto.DaoMuBattleAttribute.Builder temp = DaoMuProto.DaoMuBattleAttribute.newBuilder();
            temp.setId(entry.getKey());
            temp.setValue(entry.getValue());
            respMsg.addAttribute(temp);
        }
        respMsg.setCollectionLevel(userData.getCollectionLevel());
        // 回包
        sendPacket(Protocol.U_DAOMU_PVP_GET_BASE_ATTRIBUTE, respMsg, userId);
        return 0;
    }

    /**
     * 获取房间的人数
     *
     * @param userId
     * @return
     */
    public int getRoomUserNumByCmd(long userId) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        int roomId = userData.getRoomId();
        int floor = 1;
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData != null) {
            floor = roomData.getFloor();
        }
        DaoMuPvpProto.DaoMuPvpGetRoomUserNumRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpGetRoomUserNumRespMsg.newBuilder();
        for (DaoMuPvpRoomData data : roomDataMap.values()) {
            if (data.getFloor() != floor) {
                continue;
            }
            int num = getRoomUserList(data.getRoomId()).size();
            if (num == 0) {
                continue;
            }
            // 获取房间的人数
            DaoMuPvpProto.DaoMuPvpRoomUserNumTemp.Builder temp = DaoMuPvpProto.DaoMuPvpRoomUserNumTemp.newBuilder();
            temp.setRoomId(data.getRoomId());
            temp.setNum(num);
            respMsg.addRoom(temp);
        }
        respMsg.setRet(0);
        sendPacket(Protocol.U_DAOMU_PVP_GET_ROOM_USER_NUM, respMsg, userId);
        return 0;
    }

    /**
     * 获取结算信息
     */
    public int getSettleInfoByCmd(long userId) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        DaoMuPvpProto.DaoMuPvpGetSettleInfoRespMsg.Builder respMsg = DaoMuPvpProto.DaoMuPvpGetSettleInfoRespMsg.newBuilder();
        respMsg.setRet(0);
        respMsg.setType(userData.getOutType());
        if (userData.getOutType() == 1) {
            // 击杀者
            UserBaseInfo userBaseInfo = getUserBaseInfo(userData.getProperty().getBattleRecord().getKillUserId());
            if (userBaseInfo != null) {
                respMsg.setUserInfo(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
            }
        } else if (userData.getOutType() == 3) {
            // 怪物击杀
            respMsg.setMonsterId(userData.getProperty().getBattleRecord().getKillMonsterId());
        }
        respMsg.setSettle(parseDaoMuPvpSettleTemp(userData));
        sendPacket(Protocol.U_DAOMU_PVP_GET_SETTLE_INFO, respMsg, userId);
        return 0;
    }

    private DaoMuPvpProto.DaoMuPvpRankTemp.Builder parseDaoMuPvpRankTemp(long userId, long value, String param, int rank) {
        DaoMuPvpProto.DaoMuPvpRankTemp.Builder temp = DaoMuPvpProto.DaoMuPvpRankTemp.newBuilder();
        temp.setUserId(userId);
        UserBaseInfo userBaseInfo = getUserBaseInfo(userId);
        if (userBaseInfo != null) {
            temp.setUserInfo(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
        }
        temp.setValue(value);
        temp.setParam(param);
        temp.setRank(rank);
        return temp;
    }

    /**
     * 加入房间
     *
     * @param roomId        房间id
     * @param userData      用户信息
     * @param initBirthData 指定出生点
     */
    private void joinRoom(int roomId, DaoMuPvpUserData userData, DaoMuPvpBirthData initBirthData) {
        if (roomId == 0 || userData == null) {
            return;
        }
        long userId = userData.getUserId();
        synchronized (getRoomLock(roomId)) {
            DaoMuPvpRoomData roomData = getRoomData(roomId);
            if (roomData == null) {
                return;
            }
            if (roomData.isCollapse()) {
                return;
            }
            // 加入到新房间
            addRoomUser(roomId, userId);
        }
        userData.setRoomId(roomId);
        // 房间内随机个位置
        DaoMuPvpBirthData birthData = initBirthData == null ? randomBirth(userData.getRoomId()) : initBirthData;
        if (birthData != null) {
            // 更新位置
            userData.getPosition().setX(birthData.getCoordinate().getX());
            userData.getPosition().setY(birthData.getCoordinate().getY());
            userData.getPosition().setDirX(0);
            userData.getPosition().setDirY(0);
            userData.getPosition().setTime(System.currentTimeMillis());
        }
        // 更新下属性吧
        changeAttributeMap(userData);

        // 同步下房间内的人
        DaoMuPvpProto.DaoMuPvpJoinRoomSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpJoinRoomSync.newBuilder();
        DaoMuPvpProto.DaoMuPvpOtherPlayerInfo.Builder otherInfo = DaoMuActivityPvpPb.parseDaoMuPvpOtherPlayerInfo(userData);
        if (otherInfo != null) {
            syncMsg.setOtherInfo(otherInfo);
        }
        sendPacketToAll(Protocol.U_DAOMU_PVP_JOIN_ROOM_SYNC, syncMsg, getRoomUserListSync(userData.getRoomId()));
    }

    /**
     * 离开房间
     *
     * @param userData 用户信息
     */
    private void leaveRoom(DaoMuPvpUserData userData, int leaveType) {
        if (userData == null) {
            return;
        }
        int roomId = userData.getRoomId();
        if (roomId == 0) {
            // 没有房间
            return;
        }
        synchronized (getRoomLock(roomId)) {
            // 内存移除
            removeRoomUser(roomId, userData.getUserId());

            userData.setRoomId(0);
        }
        // 同步下
        DaoMuPvpProto.DaoMuPvpLeaveRoomSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpLeaveRoomSync.newBuilder();
        syncMsg.setUserId(userData.getUserId());
        syncMsg.setType(leaveType);
        sendPacketToAll(Protocol.U_DAOMU_PVP_LEAVE_ROOM_SYNC, syncMsg, getRoomUserListSync(roomId));
    }

    /**
     * 随机出生点 房间内
     */
    private DaoMuPvpBirthData randomBirth(int roomId) {
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        if (roomData == null) {
            return null;
        }
        List<DaoMuPvpBirthData> list = new ArrayList<>();
        int minBirthCnt = Integer.MAX_VALUE;
        for (DaoMuPvpBirthData birthData : roomData.getBirthDataList()) {
            if (birthData.getBirthCnt() > minBirthCnt) {
                continue;
            }
            if (birthData.getBirthCnt() < minBirthCnt) {
                minBirthCnt = birthData.getBirthCnt();
                list.clear();
            }
            list.add(birthData);
        }
        DaoMuPvpBirthData birthData = list.get(randomHelper.next(0, list.size()));
        birthData.setBirthCnt(birthData.getBirthCnt() + 1);
        return birthData;
    }

    /**
     * 随机出生点 本层
     */
    private DaoMuPvpBirthData randomBirthInFloor(int floor) {
        List<DaoMuPvpBirthData> list = new ArrayList<>();
        int minBirthCnt = Integer.MAX_VALUE;
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isCollapse()) {
                continue;
            }
            if (roomData.getFloor() != floor) {
                continue;
            }
            DaoMuPvpConfig pvpConfig = config.getPvpConfigMap().get(roomData.getFloor());
            if (pvpConfig == null) {
                continue;
            }
            // 判断下房间人数有没有达到上限
            if (getRoomUserList(roomData.getRoomId()).size() >= pvpConfig.getPeople()) {
                continue;
            }
            for (DaoMuPvpBirthData birthData : roomData.getBirthDataList()) {
                if (birthData.getBirthCnt() > minBirthCnt) {
                    continue;
                }
                if (birthData.getBirthCnt() < minBirthCnt) {
                    minBirthCnt = birthData.getBirthCnt();
                    list.clear();
                }
                list.add(birthData);
            }
        }
        if (list.size() == 0) {
            return null;
        }
        DaoMuPvpBirthData birthData = list.get(randomHelper.next(0, list.size()));
        birthData.setBirthCnt(birthData.getBirthCnt() + 1);
        return birthData;
    }

    private Coordinate randomDir() {
        double x = randomHelper.nextDouble(-1, 1);
        double y = (Double) randomHelper.randomList(calcDirY(x));
        return new Coordinate(x, y);
    }

    public List<Double> calcDirY(double dirX) {
        List<Double> list = new ArrayList<>();
        double sqrt = Math.sqrt(1 - dirX * dirX);
        list.add(sqrt);
        list.add(-sqrt);
        return list;
    }

    private DaoMuPvpRoomData getRoomData(int roomId) {
        return roomDataMap.get(roomId);
    }

    private DaoMuPvpUserData getUserData(long userId) {
        if (userId == 0) {
            return null;
        }
        // 不存在初始化一个
        if (!userDataMap.containsKey(userId)) {
            synchronized (getPlayerLock(userId)) {
                if (!userDataMap.containsKey(userId)) {
                    DaoMuPvpUserData userData = new DaoMuPvpUserData();
                    userData.setActivityId(config.getActivityId());
                    userData.setUserId(userId);
                    // 初始角色id
                    userData.setRoleId(config.getPvpCommonConfig().getInitRoleId());
                    // 不存在任何房间
                    userData.setRoomId(0);
                    // 初始化一下属性
                    userData.setProperty(getInitProperty());
                    userData.setInsertOption();
                    userDataMap.put(userId, userData);
                }
            }
        }
        return userDataMap.get(userId);
    }

    private DaoMuPvpProperty getInitProperty() {
        DaoMuPvpProperty property = new DaoMuPvpProperty();
        property.setLevel(1);
        property.setExp(0);
        property.setReviveCnt(0);
        property.setReviveTime(System.currentTimeMillis());
        property.setLearnSkillCnt(0);
        property.setChooseSkillList(new ArrayList<>());
        return property;
    }


    public int moveByCmd(long userId, DaoMuPvpPosition position, boolean needSync) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        // 死亡状态
        if (!userData.isLive()) {
            return GameErrorCode.E_DAOMU_PVP_ROOM_USER_IS_DIE;
        }
        playerMoveHandler(userData, position, needSync);
        return 0;
    }

    public void playerMoveHandler(DaoMuPvpUserData userData, DaoMuPvpPosition position, boolean needSync) {
        if (position.getTime() < userData.getPosition().getTime()) {
            // 位置早于当前，丢掉
            return;
        }
        // 更新下位置
        userData.getPosition().setX(calcX(position.getX()));
        userData.getPosition().setY(calcY(position.getY()));
        userData.getPosition().setDirX(position.getDirX());
        userData.getPosition().setDirY(position.getDirY());
        userData.getPosition().setTime(position.getTime());
        // 广播
        if (needSync) {
            addPlayerPosition(userData.getRoomId(), userData.getUserId(), userData.getPosition());
        }
    }

    /**
     * 位置定时同步
     * 帧同步
     * 33ms
     * 1秒30帧
     */
    public void syncPositionTask() {
        for (Integer roomId : roomDataMap.keySet()) {
            Map<Long, DaoMuPvpPosition> playerMap = new ConcurrentHashMap<>();
            synchronized (needSyncPlayerPositionMap) {
                playerMap = needSyncPlayerPositionMap.getOrDefault(roomId, new ConcurrentHashMap<>());
                needSyncPlayerPositionMap.put(roomId, new ConcurrentHashMap<>());
            }
            Map<String, DaoMuPvpPosition> monsterMap = new ConcurrentHashMap<>();
            synchronized (needSyncMonsterPositionMap) {
                monsterMap = needSyncMonsterPositionMap.getOrDefault(roomId, new ConcurrentHashMap<>());
                needSyncMonsterPositionMap.put(roomId, new ConcurrentHashMap<>());
            }
            // 玩家位置
            DaoMuPvpProto.DaoMuPvpRoomPositionSync.Builder syncMsg = DaoMuPvpProto.DaoMuPvpRoomPositionSync.newBuilder();
            for (Map.Entry<Long, DaoMuPvpPosition> entry : playerMap.entrySet()) {
                DaoMuPvpProto.DaoMuPvpSimplePositionTemp.Builder temp = DaoMuPvpProto.DaoMuPvpSimplePositionTemp.newBuilder();
                temp.setUserId(entry.getKey());
                temp.setPosition(DaoMuActivityPvpPb.parseDaoMuPvpPlayerPositionData(entry.getValue()));
                syncMsg.addPosition(temp);
            }
            // 怪物位置
            for (Map.Entry<String, DaoMuPvpPosition> entry : monsterMap.entrySet()) {
                DaoMuPvpProto.DaoMuPvpMonsterPositionTemp.Builder temp = DaoMuPvpProto.DaoMuPvpMonsterPositionTemp.newBuilder();
                temp.setMonsterId(entry.getKey());
                temp.setPosition(DaoMuActivityPvpPb.parseDaoMuPvpPlayerPositionData(entry.getValue()));
                syncMsg.addMonsterPosition(temp);
            }
            // 有变动才发
            if (syncMsg.getPositionCount() > 0 || syncMsg.getMonsterPositionCount() > 0) {
                sendPacketToAll(Protocol.U_DAOMU_PVP_POSITION, syncMsg, getRoomUserListSync(roomId));
            }
        }


    }

    /**
     * 广播位置
     */
    private void addPlayerPosition(int roomId, long userId, DaoMuPvpPosition position) {
        synchronized (needSyncPlayerPositionMap) {
            Map<Long, DaoMuPvpPosition> roomMap = needSyncPlayerPositionMap.getOrDefault(roomId, new ConcurrentHashMap<>());
            roomMap.put(userId, position.clone());
            needSyncPlayerPositionMap.put(roomId, roomMap);
        }
    }

    /**
     * 广播位置
     */
    private void addMonsterPosition(int roomId, String monsterId, DaoMuPvpPosition position) {
        synchronized (needSyncMonsterPositionMap) {
            Map<String, DaoMuPvpPosition> roomMap = needSyncMonsterPositionMap.getOrDefault(roomId, new ConcurrentHashMap<>());
            roomMap.put(monsterId, position.clone());
            needSyncMonsterPositionMap.put(roomId, roomMap);
        }
    }

    /**
     * 心跳
     */
    public int pingByCmd(long userId, int type) {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return GameErrorCode.E_DAOMU_PVP_NOT_BATTLE_TIME;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return GameErrorCode.E_DAOMU_PVP_USER_DATA_NOT_FOUND;
        }
        if (type == 0) {
            // 更新心跳时间
            userData.setPingTime(System.currentTimeMillis());
            // 检测buff变动
            if (userData.getProperty().checkBuff()) {
                // 重新计算战斗属性
                changeAttributeMap(userData);
            }
        } else if (type == 1) {
            // 离开房间，不参与同步,心跳时间改掉
            userData.setPingTime(-1);
        }

        return 0;
    }

    private Set<Long> npcSet = new HashSet<>();

    /**
     * 寻找最短距离的玩家
     */
    public long findMinDistanceUserId(DaoMuPvpUserData userData) {
        if (userData == null) {
            return 0;
        }
        long minDistanceUserId = 0;
        double minDistance = Double.MAX_VALUE;
        ConcurrentHashSet<Long> roomUserList = getRoomUserList(userData.getRoomId());
        for (Long userId : roomUserList) {
            if (userData.getUserId() == userId) {
                continue;
            }
            // npc跳过
//            if (npcSet.contains(userId)) {
//                continue;
//            }
            DaoMuPvpUserData targetUserData = getUserData(userId);
            if (targetUserData == null || targetUserData.getRoomId() != userData.getRoomId()) {
                continue;
            }
            double distance = distance(userData.getCoordinate(), targetUserData.getCoordinate());
            if (distance < minDistance) {
                minDistance = distance;
                minDistanceUserId = targetUserData.getUserId();
            }
        }
        return minDistanceUserId;
    }

    /**
     * 寻找最短距离的玩家
     */
    public long findMinDistanceUserId(DaoMuPvpMonsterData monsterData, Map<Long, Integer> lockUserCntMap) {
        if (monsterData == null) {
            return 0;
        }
        long minDistanceUserId = 0;
        double minDistance = Double.MAX_VALUE;
        long nowTime = System.currentTimeMillis();
        ConcurrentHashSet<Long> roomUserList = getRoomUserList(monsterData.getRoomId());
        for (Long userId : roomUserList) {
            if (lockUserCntMap.containsKey(userId) && lockUserCntMap.get(userId) >= config.getPvpCommonConfig().getMobsHateTargetNumMax()) {
                continue;
            }
            DaoMuPvpUserData targetUserData = getUserData(userId);
            if (targetUserData == null || targetUserData.getRoomId() != monsterData.getRoomId()) {
                continue;
            }
            // 心跳超过时间不锁定
            if (targetUserData.getPingTime() == -1 || nowTime - targetUserData.getPingTime() > 10 * DateHelper.SECOND_MILLIONS) {
                continue;
            }
            double distance = distance(monsterData.getCoordinate(), targetUserData.getCoordinate());
            if (distance < minDistance) {
                minDistance = distance;
                minDistanceUserId = targetUserData.getUserId();
            }
        }
        return minDistanceUserId;
    }

    /**
     * 寻找距离小于xxx的玩家
     */
    public List<DaoMuPvpUserData> findDistanceUserData(DaoMuPvpUserData userData, double distance) {
        List<DaoMuPvpUserData> list = new ArrayList<>();
        if (userData == null) {
            return list;
        }
        ConcurrentHashSet<Long> roomUserList = getRoomUserList(userData.getRoomId());
        for (Long userId : roomUserList) {
            if (userData.getUserId() == userId) {
                continue;
            }
            DaoMuPvpUserData targetUserData = getUserData(userId);
            if (targetUserData == null || targetUserData.getRoomId() != userData.getRoomId()) {
                continue;
            }
            if (distance(userData.getCoordinate(), targetUserData.getCoordinate()) <= distance) {
                list.add(targetUserData);
            }
        }
        return list;
    }

    /**
     * 寻找距离小于xxx的宝箱
     */
    public List<DaoMuPvpBoxData> findDistanceBoxData(DaoMuPvpUserData userData, double distance) {
        List<DaoMuPvpBoxData> list = new ArrayList<>();
        if (userData == null) {
            return list;
        }
        DaoMuPvpRoomData roomData = getRoomData(userData.getRoomId());
        for (DaoMuPvpBoxData boxData : roomData.getBoxDataList()) {
            if (boxData == null || !boxData.isLive()) {
                continue;
            }
            if (distance(userData.getCoordinate(), boxData.getCoordinate()) <= distance) {
                list.add(boxData);
            }
        }
        return list;
    }

    /**
     * 更新存活榜单
     */
    public void aliveRankChangeTask() {
        if (!isBattleTime() || daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        for (DaoMuPvpUserData userData : userDataMap.values()) {
            if (userData == null || !userData.isAble() || !userData.isApply() || userData.isOut()) {
                continue;
            }
            // 这里只更新存活的人
            // param 等级#经验#经验更新时间#锁定时藏品分
            List<String> paramList = new ArrayList<>();
            paramList.add(String.valueOf(userData.getProperty().getLevel()));
            paramList.add(String.valueOf(userData.getProperty().getExp()));
            paramList.add(String.valueOf(userData.getProperty().getExpUpdateTime()));
            paramList.add(String.valueOf(userData.getCollectionScore()));
            aliveRankListModel.rankChange(new DaoMuRank(userData.getUserId(), nowTime, StringUtils.listToString(paramList, "#"), nowTime));
        }
    }


    /**
     * 检测结算
     */
    public void checkSettleTask() {
        if (daoMuPvpData.isSettle()) {
            return;
        }
        long nowTime = System.currentTimeMillis();
        // 战斗结束或者淘汰模式下生存人数小于等于0
        if (nowTime >= getBattleEndTime() || (isBattleTime() && daoMuPvpData.isDisuse()&& aliveCnt.get() <= 1)) {

            if (aliveCnt.get() == 1) {
                try {
                    logger.info("checkSettleTask, activityId :{},aliveCnt is 1", config.getActivityId());
                } catch (Exception e) {
                    logger.error("checkSettleTask error", e);
                }
                // 剩最后一个人时，再变更一下榜单,防止数据对不上
                aliveRankChangeTask();
            }

            // 触发结算
            settle();

            // 清除房间内数据，释放内存
            for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
                roomData.clearData();
            }
        }
    }

    /**
     * 结算
     */
    private void settle() {
        if (daoMuPvpData.isSettle()) {
            return;
        }
        long winUserId = 0;
        DaoMuPvpProto.DaoMuPvpSettleSyncMsg.Builder syncMsg = DaoMuPvpProto.DaoMuPvpSettleSyncMsg.newBuilder();
        synchronized (lock) {
            if (daoMuPvpData.isSettle()) {
                return;
            }
            long nowTime = System.currentTimeMillis();
            // 标识结算
            daoMuPvpData.setSettle(true);
            // 开始结算
            int rankNum = 0;
            for (DaoMuRank rank : aliveRankListModel.getRankList()) {
                rankNum++;
                DaoMuPvpUserData userData = getUserData(rank.getUserId());
                if (userData == null) {
                    continue;
                }
                // 记录下第一名的信息
                if (rankNum == 1) {
                    winUserId = rank.getUserId();
                }
                DaoMuPvpProto.DaoMuPvpSettleTemp.Builder temp = DaoMuPvpProto.DaoMuPvpSettleTemp.newBuilder();
                temp.setUserId(rank.getUserId());
                UserBaseInfo userBaseInfo = getUserBaseInfo(rank.getUserId());
                if (userBaseInfo != null) {
                    temp.setUserInfo(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
                }
                temp.setRank(rankNum);
                temp.setUserId(rank.getUserId());
                temp.setOutTime(rank.getValue());
                syncMsg.addSettle(temp);

                // 记录下分数
                long addRankScore = addRankScore(rank.getUserId(), daoMuPvpData.getRound(), rankNum, rank.getTime());

                // 记录下日志
                CrossAutoLogMgr.add(new LogDaoMuPvpSettle(
                        config.getActivityId(),
                        daoMuPvpData.getRound(),
                        userData.getUserId(),
                        userData.getRoleId(),
                        userData.getProperty().getLevel(),
                        userData.getProperty().getExp(),
                        JSON.toJSONString(userData.getProperty().getSkillDataList()),
                        getFloorByRoom(userData.getRoomId()),
                        userData.getProperty().getReviveCnt(),
                        userData.getProperty().getBattleRecord().getTotalKillNum(),
                        (rank.getTime() - getBattleStartTime()) / 1000,
                        rankNum,
                        addRankScore
                ));
            }
        }
//        sendPacketToAll(Protocol.U_DAOMU_PVP_SETTLE_SYNC, syncMsg, getRoomUserListSync(-1));
        // 改为只同步给冠军
        sendPacket(Protocol.U_DAOMU_PVP_SETTLE_SYNC, syncMsg, winUserId);
    }

    private int getFloorByRoom(int roomId) {
        DaoMuPvpRoomData roomData = getRoomData(roomId);
        return roomData == null ? 0 : roomData.getFloor();
    }

    /**
     * 获得榜单积分
     */
    private long addRankScore(long userId, int round, int rankNum, long updateTime) {
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return 0;
        }
        DaoMuPvpRankConfig rankConfig = config.getDaoMuPvpRankConfig(round, rankNum);
        if (rankConfig == null) {
            return 0;
        }
        long addScore = rankConfig.getScore();
        // 记录下总积分
        userData.setScore(userData.getScore() + addScore);

        // 添加积分记录
        userData.getRoundRank().put(round, rankNum);
        userData.getRoundScore().put(round, addScore);

        userData.setUpdateOption();

        // 更新榜单
        changeScoreRank(userData, updateTime);

        return addScore;
    }

    public UserBaseInfo getUserBaseInfo(long userId) {
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        if (userBaseInfo != null) {
            return userBaseInfo;
        }
        CrossUserBaseInfo crossNpcUserBaseInfo = CrossNpcUserMgr.getCrossNpcUserBaseInfo(userId);
        if (crossNpcUserBaseInfo != null) {
            return crossNpcUserBaseInfo.getUserBaseInfo();
        }
        return null;
    }

    /**
     * 更新个人积分排行
     */
    public void changeScoreRank(DaoMuPvpUserData userData, long updateTime) {
        if (userData == null) {
            return;
        }
        UserBaseInfo userBaseInfo = getUserBaseInfo(userData.getUserId());
        if (userBaseInfo == null) {
            return;
        }
        CrossUserRank crossUserRank = new CrossUserRank();
        crossUserRank.setUserId(userData.getUserId());
        crossUserRank.setValue(BigInteger.valueOf(userData.getScore()));
        crossUserRank.setType(config.getActivityId());
        crossUserRank.setBigRankType(eGamePlayerEventType.DaoMuPvpPersonRank.getValue());
        crossUserRank.setUserBaseInfo(userBaseInfo);
        // 封装额外参数   最后一轮存活时间#最后一轮等级#最后一轮经验#最后一轮经验更新时间#最后一轮锁定时藏品分
        List<String> paramList = new ArrayList<>();
        paramList.add(String.valueOf(updateTime));
        paramList.add(String.valueOf(userData.getProperty().getLevel()));
        paramList.add(String.valueOf(userData.getProperty().getExp()));
        paramList.add(String.valueOf(userData.getProperty().getExpUpdateTime()));
        paramList.add(String.valueOf(userData.getCollectionScore()));
        String paramStr = StringUtils.listToString(paramList, "#");
        crossUserRank.setParam(paramStr);
        ActivityRankMgr.changeActivityNewRank(config.getActivityId(), eGamePlayerEventType.DaoMuPvpPersonRank.getValue(), crossUserRank);

        // 更新下榜单condition
        notifyScoreCondition(userData.getUserId(), userData.getScore(), paramStr);
    }

    /**
     * 实时战斗属性
     * 属性变动需要重新计算
     * buff增减，技能增减
     * 使用时调用这个
     */
    public void changeAttributeMap(DaoMuPvpUserData userData) {
        if (userData == null) {
            return;
        }
        DaoMuPvpProperty property = userData.getProperty();
        // 校验下buff
        property.checkBuff();
        // 回血技能需要特殊处理下
        long oldRecoverHp = userData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_RECOVER_HP_PVP.getType(), 0L);
        // 血量上限技能需要特殊处理下
        long oldHpLimit = userData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_HP.getType(), 0L);

        // 基础属性
        Map<Integer, Long> attributeMap = new ConcurrentHashMap<>(property.getBaseAttributeMap());
        // 属性加成map
        Map<Integer, Long> additionMap = new HashMap<>();
        // 技能加成
        for (DaoMuPvpSkillData skillData : property.getSkillDataList()) {
            //1	武器的恢复速度
            //2	武器可携带最大数量
            //3	武器的转速
            //4	伙伴移速
            //5	伙伴攻击力
            //6	经验拾取距离
            //7	每秒生命恢复
            //8 提高血量固定值
            //9 提高防御固定值
            DaoMuPvpSkillConfig skillConfig = config.getDaoMuPvpSkillConfig(skillData.getSkillId(), skillData.getLevel());
            List<Long> paramList = StringUtils.stringToLongList(skillConfig.getSkillPara(), "\\|");
            additionMap.put(ATTRIBUTE_BULLET_RECOVER.getType(), additionMap.getOrDefault(ATTRIBUTE_BULLET_RECOVER.getType(), 0L) + paramList.get(0));
            additionMap.put(ATTRIBUTE_BULLET_LIMIT.getType(), additionMap.getOrDefault(ATTRIBUTE_BULLET_LIMIT.getType(), 0L) + paramList.get(1));
            additionMap.put(ATTRIBUTE_BULLET_ROTATIONAL_SPEED.getType(), additionMap.getOrDefault(ATTRIBUTE_BULLET_ROTATIONAL_SPEED.getType(), 0L) + paramList.get(2));
            additionMap.put(ATTRIBUTE_SPEED.getType(), additionMap.getOrDefault(ATTRIBUTE_SPEED.getType(), 0L) + paramList.get(3));
            additionMap.put(ATTRIBUTE_ATT.getType(), additionMap.getOrDefault(ATTRIBUTE_ATT.getType(), 0L) + paramList.get(4));
            additionMap.put(ATTRIBUTE_PICKING_DISTANCE.getType(), additionMap.getOrDefault(ATTRIBUTE_PICKING_DISTANCE.getType(), 0L) + paramList.get(5));
            additionMap.put(ATTRIBUTE_RECOVER_HP_PVP.getType(), additionMap.getOrDefault(ATTRIBUTE_RECOVER_HP_PVP.getType(), 0L) + paramList.get(6));
            additionMap.put(ATTRIBUTE_HP.getType(), additionMap.getOrDefault(ATTRIBUTE_HP.getType(), 0L) + paramList.get(7));
            additionMap.put(ATTRIBUTE_DEFEND.getType(), additionMap.getOrDefault(ATTRIBUTE_DEFEND.getType(), 0L) + paramList.get(8));
        }
        for (DaoMuPvpBuffData buffData : property.getBuffDataList()) {
            if (buffData.getBuffId() == DaoMuPvpBuffType.Attack.getBuffType()) {
                additionMap.put(ATTRIBUTE_ATT.getType(), additionMap.getOrDefault(ATTRIBUTE_ATT.getType(), 0L) + buffData.getValue());
            } else if (buffData.getBuffId() == DaoMuPvpBuffType.Speed.getBuffType()) {
                additionMap.put(ATTRIBUTE_SPEED.getType(), additionMap.getOrDefault(ATTRIBUTE_SPEED.getType(), 0L) + buffData.getValue());
            }
        }
        // 计算属性
        for (Map.Entry<Integer, Long> entry : attributeMap.entrySet()) {
            DaoMuAttributeEnum attributeEnum = forValue(entry.getKey());
            if (attributeEnum == null) {
                continue;
            }
            switch (attributeEnum) {
                case ATTRIBUTE_ATT:
                    // 攻击加成 千分比
//                    attributeMap.put(ATTRIBUTE_ATT, BigDecimal.valueOf(attributeMap.getOrDefault(ATTRIBUTE_ATT, 0L)).multiply(BigDecimal.valueOf(1000 + additionMap.getOrDefault(ATTRIBUTE_ATT, 0L))).divide(BigDecimal.valueOf(1000), 0, RoundingMode.DOWN).longValue());
                    // 现改为固定值
                    attributeMap.put(ATTRIBUTE_ATT.getType(), attributeMap.getOrDefault(ATTRIBUTE_ATT.getType(), 0L) + additionMap.getOrDefault(ATTRIBUTE_ATT.getType(), 0L));
                    break;
                case ATTRIBUTE_BULLET_RECOVER:
                    // 武器恢复间隔  千分比
                    attributeMap.put(ATTRIBUTE_BULLET_RECOVER.getType(), BigDecimal.valueOf(attributeMap.getOrDefault(ATTRIBUTE_BULLET_RECOVER.getType(), 0L)).multiply(BigDecimal.valueOf(1000 + additionMap.getOrDefault(ATTRIBUTE_BULLET_RECOVER.getType(), 0L))).divide(BigDecimal.valueOf(1000), 0, RoundingMode.DOWN).longValue());
                    break;
                case ATTRIBUTE_BULLET_LIMIT:
                    // 武器数量上限
                    attributeMap.put(ATTRIBUTE_BULLET_LIMIT.getType(), attributeMap.getOrDefault(ATTRIBUTE_BULLET_LIMIT.getType(), 0L) + additionMap.getOrDefault(ATTRIBUTE_BULLET_LIMIT.getType(), 0L));
                    break;
                case ATTRIBUTE_BULLET_ROTATIONAL_SPEED:
                    // 转速 千分比
                    attributeMap.put(ATTRIBUTE_BULLET_ROTATIONAL_SPEED.getType(), BigDecimal.valueOf(attributeMap.getOrDefault(ATTRIBUTE_BULLET_ROTATIONAL_SPEED.getType(), 0L)).multiply(BigDecimal.valueOf(1000 + additionMap.getOrDefault(ATTRIBUTE_BULLET_ROTATIONAL_SPEED.getType(), 0L))).divide(BigDecimal.valueOf(1000), 0, RoundingMode.DOWN).longValue());
                    break;
                case ATTRIBUTE_SPEED:
                    // 移速 千分比
                    attributeMap.put(ATTRIBUTE_SPEED.getType(), BigDecimal.valueOf(attributeMap.getOrDefault(ATTRIBUTE_SPEED.getType(), 0L)).multiply(BigDecimal.valueOf(1000 + additionMap.getOrDefault(ATTRIBUTE_SPEED.getType(), 0L))).divide(BigDecimal.valueOf(1000), 0, RoundingMode.DOWN).longValue());
                    break;
                case ATTRIBUTE_PICKING_DISTANCE:
                    // 拾取范围 固定值
                    attributeMap.put(ATTRIBUTE_PICKING_DISTANCE.getType(), attributeMap.getOrDefault(ATTRIBUTE_PICKING_DISTANCE.getType(), 0L) + additionMap.getOrDefault(ATTRIBUTE_PICKING_DISTANCE.getType(), 0L));
                    break;
                case ATTRIBUTE_RECOVER_HP_PVP:
                    // 回血速度 固定值
                    attributeMap.put(ATTRIBUTE_RECOVER_HP_PVP.getType(), attributeMap.getOrDefault(ATTRIBUTE_RECOVER_HP_PVP.getType(), 0L) + additionMap.getOrDefault(ATTRIBUTE_RECOVER_HP_PVP.getType(), 0L));
                    break;
                case ATTRIBUTE_HP:
                    // 回血速度 固定值
                    attributeMap.put(ATTRIBUTE_HP.getType(), attributeMap.getOrDefault(ATTRIBUTE_HP.getType(), 0L) + additionMap.getOrDefault(ATTRIBUTE_HP.getType(), 0L));
                    break;
                case ATTRIBUTE_DEFEND:
                    // 回血速度 固定值
                    attributeMap.put(ATTRIBUTE_DEFEND.getType(), attributeMap.getOrDefault(ATTRIBUTE_DEFEND.getType(), 0L) + additionMap.getOrDefault(ATTRIBUTE_DEFEND.getType(), 0L));
                    break;
                default:
                    break;
            }
        }

        // 处理下上限
        for (Map.Entry<DaoMuAttributeEnum, Long> entry : config.getPvpCommonConfig().getMaxAttributeMap().entrySet()) {
            DaoMuAttributeEnum attributeEnum = entry.getKey();
            Long maxValue = entry.getValue();
            if (!attributeMap.containsKey(attributeEnum.getType())) {
                continue;
            }
            attributeMap.put(attributeEnum.getType(), Math.min(maxValue, attributeMap.getOrDefault(attributeEnum.getType(), 0L)));
        }

        // 更新战斗属性
        property.setAttributeMap(attributeMap);

        long newRecoverHp = userData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_RECOVER_HP_PVP.getType(), 0L);
        if (oldRecoverHp == 0 && newRecoverHp > 0) {
            // 记录下首次回血时间
            userData.getProperty().setLastHpRecoverTime(System.currentTimeMillis());
        }

        long newHpLimit = userData.getProperty().getAttributeMap().getOrDefault(ATTRIBUTE_HP.getType(), 0L);

        // 提升血量上限时也要回下血
        if (newHpLimit != oldHpLimit) {
            userData.getProperty().addHp(newHpLimit - oldHpLimit);
        }

        // 广播下属性
        notifyUserProperty(userData, true);
    }

    /**
     * 公告列表
     */
    private LinkedList<DaoMuPvpNoticeData> noticeDataList = new LinkedList<>();

    /**
     * 添加公告
     */
    public void addNotice(long userId1, long userId2, int type, String content) {
        DaoMuPvpNoticeData noticeData = new DaoMuPvpNoticeData();
        if (userId1 != 0) {
            UserBaseInfo userBaseInfo = getUserBaseInfo(userId1);
            if (userBaseInfo != null) {
                DaoMuPvpNoticeUserData user1 = new DaoMuPvpNoticeUserData();
                user1.setServerId(userBaseInfo.getServerId());
                user1.setNickName(userBaseInfo.getNickName());
                user1.setDecoration(userBaseInfo.getDecoration());
                noticeData.setUser1(user1);
            }
        }
        if (userId2 != 0) {
            UserBaseInfo userBaseInfo = getUserBaseInfo(userId2);
            if (userBaseInfo != null) {
                DaoMuPvpNoticeUserData user2 = new DaoMuPvpNoticeUserData();
                user2.setServerId(userBaseInfo.getServerId());
                user2.setNickName(userBaseInfo.getNickName());
                user2.setDecoration(userBaseInfo.getDecoration());
                noticeData.setUser2(user2);
            }
        }
        noticeData.setType(type);
        noticeData.setContent(content);
        noticeData.setTime(System.currentTimeMillis());

        // 加入公告列表
        synchronized (noticeDataList) {
            if (noticeDataList.size() >= config.getPvpCommonConfig().getNoticeMaxNum()) {
                noticeDataList.removeFirst();
            }
            noticeDataList.addLast(noticeData);
        }
        // 广播
        DaoMuPvpProto.DaoMuPvpNoticeSyncMsg.Builder syncMsg = DaoMuPvpProto.DaoMuPvpNoticeSyncMsg.newBuilder();
        syncMsg.setNotice(DaoMuActivityPvpPb.parseDaoMuPvpNoticeTemp(noticeData));
        sendPacketToAll(Protocol.U_DAOMU_PVP_NOTICE_SYNC, syncMsg, getRoomUserListSync(-1));
    }

    /**
     * 通知 condition 变化
     */
    public void notifyAddExp(long userId, long addExp) {
        DaoMuProto.DaoMuNotifyConditionRespMsg.Builder builder = DaoMuProto.DaoMuNotifyConditionRespMsg.newBuilder();
        builder.setActivityId(config.getActivityId());
        builder.setUserId(userId);
        builder.setConditionType(eGamePlayerEventType.DaoMuPvpExp.getValue());
        builder.setValue(addExp);
        builder.setParam(String.valueOf(getCurRound()));
        sendPacket(Protocol.S_DAOMU_CONDITION_VALUE_CHANGE_FROM_CROSS, builder, userId);
    }

    /**
     * 通知 condition 变化
     */
    public void notifyScoreCondition(long userId, long score, String paramStr) {
        DaoMuProto.DaoMuNotifyConditionRespMsg.Builder builder = DaoMuProto.DaoMuNotifyConditionRespMsg.newBuilder();
        builder.setActivityId(config.getActivityId());
        builder.setUserId(userId);
        builder.setConditionType(eGamePlayerEventType.DaoMuPvpPersonRank.getValue());
        builder.setValue(score);
        builder.setParam(paramStr);
        sendPacket(Protocol.S_DAOMU_CONDITION_VALUE_CHANGE_FROM_CROSS, builder, userId);
    }


    /**
     * 测试指令
     */
    public void debug(long userId, String code, String param) {
        if (ConfigHelper.getInt("channelId") != 666) {
            return;
        }
        DaoMuPvpUserData userData = getUserData(userId);
        if (userData == null) {
            return;
        }
        // 生成npc
        if (code.equals("/dmnpc")) {
            for (int i = 0; i < Integer.parseInt(param); i++) {
                CrossUserBaseInfo npcUserBaseInfo = CrossNpcUserMgr.createCrossNpcUserBaseInfo();
                DaoMuPvpUserData npc = getUserData(npcUserBaseInfo.getUserId());
                if (npc == null) {
                    continue;
                }
                npc.setOut(false);
                npc.setApply(true);
                npc.setAble(true);
                npc.setCollectionLevel(randomHelper.next(1,51));
                npc.setCollectionScore(560);
                allotUser(npc.getUserId());
                npcSet.add(npcUserBaseInfo.getUserId());
            }
        } else if (code.equals("/dmtallnpc")) {
            // 清空npc
            int roomId = Integer.parseInt(param);
            ConcurrentHashSet<Long> roomUserList = getRoomUserList(roomId);
            // 移除掉
            for (Long targetUserId : roomUserList) {
                if (!npcSet.contains(targetUserId)) {
                    continue;
                }
                DaoMuPvpUserData target = getUserData(targetUserId);
                if (target == null) {
                    continue;
                }
                leaveRoom(target, 1);
            }
        } else if (code.equals("/dmenter")) {
            int roomId = Integer.parseInt(param);
            leaveRoom(userData, 1);
            joinRoom(roomId, userData, null);
        } else if (code.equals("/dmto")) {
            String[] split = param.split(",");
            userData.getPosition().setX(Integer.parseInt(split[0]));
            userData.getPosition().setY(Integer.parseInt(split[1]));
            userData.getPosition().setTime(System.currentTimeMillis());
            // 同步下
            addPlayerPosition(userData.getRoomId(), userData.getUserId(), userData.getPosition());
        } else if (code.equals("/dmtaotai")) {
            daoMuPvpData.setDisuse(true);
        } else if (code.equals("/dmtanta")) {
            DaoMuPvpRoomData roomData = getRoomData(userData.getRoomId());
            // 处理坍塌
            handleCollapse(roomData.getFloor());
            // 坍塌公告
            addNotice(0, 0, DaoMuPvpNoticeType.COLLAPSE.getType(), String.valueOf(roomData.getFloor()));
        } else if (code.equals("/dmshouquan")) {
            for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
                roomData.setShrinkStartTime(System.currentTimeMillis());
                // 需要广播下
                notifyRoomData(roomData.getRoomId());
            }
            // 收圈公告
            addNotice(0, 0, DaoMuPvpNoticeType.START_SHRINK_CIRCLE.getType(), "");
        } else if (code.equals("/dmsx")) {
            String[] split = param.split("=");
            userData.getProperty().getBaseAttributeMap().put(Integer.parseInt(split[0]), Long.parseLong(split[1]));
            changeAttributeMap(userData);
        } else if (code.equals("/dmnotice")) {
            List<String> list = StringUtils.stringToStringList(param, "=");
            int type = 0;
            String noticeParam = "";
            if (list.size() >= 1) {
                type = Integer.parseInt(list.get(0));
            }
            if (list.size() >= 2) {
                noticeParam = list.get(1);
            }
            addNotice(userId, userId, type, noticeParam);
        } else if (code.equals("/dmexp")) {
            addExp(userData, Long.parseLong(param));
        } else if (code.equals("/dmrank")) {
            addRankScore(userId, getCurRound(), 1, System.currentTimeMillis());
        } else if (code.equals("/dmaddhp")) {
            userData.getProperty().addHp(Long.parseLong(param));
            notifyUserProperty(userData, true);
        }
    }

    /**
     * 数据保存
     */
    public void save() {
        if (daoMuPvpData != null) {
            if (daoMuPvpData.isInsertOption()) {
                DaoMuActivityBusiness.addDaoMuPvpData(daoMuPvpData);
            } else if (daoMuPvpData.isUpdateOption()) {
                DaoMuActivityBusiness.updateDaoMuPvpData(daoMuPvpData);
            }
        }
        for (DaoMuPvpRoomData roomData : roomDataMap.values()) {
            if (roomData.isInsertOption()) {
                DaoMuActivityBusiness.addDaoMuPvpRoomData(roomData);
            } else if (roomData.isUpdateOption()) {
                DaoMuActivityBusiness.updateDaoMuPvpRoomData(roomData);
            }
        }
        for (DaoMuPvpUserData userData : userDataMap.values()) {
            if (npcSet.contains(userData.getUserId())) {
                continue;
            }
            if (userData.isInsertOption()) {
                DaoMuActivityBusiness.addDaoMuPvpUserData(userData);
            } else if (userData.isUpdateOption()) {
                DaoMuActivityBusiness.updateDaoMuPvpUserData(userData);
            }
        }
    }


}
